Merge branch 'feature/thangload'

This commit is contained in:
Scott Erickson 2014-05-14 11:48:19 -07:00
commit 7a381df348
25 changed files with 292 additions and 111 deletions

View file

@ -2,7 +2,7 @@ var window = self;
var Global = self;
importScripts("/javascripts/lodash.js", "/javascripts/aether.js");
console.log("imported scripts!");
console.log("Aether Tome worker has finished importing scripts.");
var aethers = {};
var createAether = function (spellKey, options)
@ -96,4 +96,4 @@ self.addEventListener('message', function(e) {
var returnObject = {"message":message, "function":"none"};
self.postMessage(JSON.stringify(returnObject));
}
}, false);
}, false);

View file

@ -8,4 +8,9 @@ module.exports = class CocoCollection extends Backbone.Collection
model.loaded = true for model in @models
getURL: ->
return if _.isString @url then @url else @url()
return if _.isString @url then @url else @url()
fetch: ->
@jqxhr = super(arguments...)
@loading = true
@jqxhr

View file

@ -0,0 +1,14 @@
ThangType = require 'models/ThangType'
CocoCollection = require 'collections/CocoCollection'
module.exports = class ThangNamesCollection extends CocoCollection
url: '/db/thang.type/names'
model: ThangType
isCachable: false
constructor: (@ids) -> super()
fetch: (options) ->
options ?= {}
_.extend options, {type:'POST', data:{ids:@ids}}
super(options)

View file

@ -4,6 +4,7 @@ LevelSystem = require 'models/LevelSystem'
Article = require 'models/Article'
LevelSession = require 'models/LevelSession'
ThangType = require 'models/ThangType'
ThangNamesCollection = require 'collections/ThangNamesCollection'
CocoClass = require 'lib/CocoClass'
AudioPlayer = require 'lib/AudioPlayer'
@ -21,8 +22,10 @@ World = require 'lib/world/world'
module.exports = class LevelLoader extends CocoClass
constructor: (options) ->
@t0 = new Date().getTime()
super()
@supermodel = options.supermodel
@supermodel.setMaxProgress 0.2
@levelID = options.levelID
@sessionID = options.sessionID
@opponentSessionID = options.opponentSessionID
@ -103,17 +106,18 @@ module.exports = class LevelLoader extends CocoClass
objUniq = (array) -> _.uniq array, false, (arg) -> JSON.stringify(arg)
for thangID in _.uniq thangIDs
url = "/db/thang.type/#{thangID}/version"
url += "?project=true" if @headless and not @editorMode
res = @maybeLoadURL url, ThangType, 'thang'
@listenToOnce res.model, 'sync', @buildSpriteSheetsForThangType if res
worldNecessities = []
@thangIDs = _.uniq thangIDs
@thangNames = new ThangNamesCollection(@thangIDs)
worldNecessities.push @supermodel.loadCollection(@thangNames, 'thang_names')
for obj in objUniq componentVersions
url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
@maybeLoadURL url, LevelComponent, 'component'
worldNecessities.push @maybeLoadURL(url, LevelComponent, 'component')
for obj in objUniq systemVersions
url = "/db/level.system/#{obj.original}/version/#{obj.majorVersion}"
@maybeLoadURL url, LevelSystem, 'system'
worldNecessities.push @maybeLoadURL(url, LevelSystem, 'system')
for obj in objUniq articleVersions
url = "/db/article/#{obj.original}/version/#{obj.majorVersion}"
@maybeLoadURL url, Article, 'article'
@ -125,16 +129,51 @@ module.exports = class LevelLoader extends CocoClass
wizard = ThangType.loadUniversalWizard()
@supermodel.loadModel wizard, 'thang'
jqxhrs = (resource.jqxhr for resource in worldNecessities when resource?.jqxhr)
$.when(jqxhrs...).done(@onWorldNecessitiesLoaded)
onWorldNecessitiesLoaded: =>
@initWorld()
@supermodel.clearMaxProgress()
return if @headless and not @editorMode
thangsToLoad = _.uniq( (t.spriteName for t in @world.thangs) )
nameModelTuples = ([thangType.get('name'), thangType] for thangType in @thangNames.models)
nameModelMap = _.zipObject nameModelTuples
@spriteSheetsToBuild = []
for thangTypeName in thangsToLoad
thangType = nameModelMap[thangTypeName]
thangType.fetch()
thangType = @supermodel.loadModel(thangType, 'thang').model
res = @supermodel.addSomethingResource "sprite_sheet", 5
res.thangType = thangType
res.markLoading()
@spriteSheetsToBuild.push res
@buildLoopInterval = setInterval @buildLoop, 5
maybeLoadURL: (url, Model, resourceName) ->
return if @supermodel.getModel(url)
model = new Model().setURL url
@supermodel.loadModel(model, resourceName)
onSupermodelLoaded: ->
console.log 'SuperModel for Level loaded in', new Date().getTime() - @t0, 'ms'
@loadLevelSounds()
@denormalizeSession()
app.tracker.updatePlayState(@level, @session) unless @headless
@initWorld()
buildLoop: =>
return if @lastBuilt and new Date().getTime() - @lastBuilt < 10
return clearInterval @buildLoopInterval unless @spriteSheetsToBuild.length
for spriteSheetResource, i in @spriteSheetsToBuild
if spriteSheetResource.thangType.loaded
@buildSpriteSheetsForThangType spriteSheetResource.thangType
@spriteSheetsToBuild.splice i, 1
@lastBuilt = new Date().getTime()
spriteSheetResource.markLoaded()
return
denormalizeSession: ->
return if @headless or @sessionDenormalized or @spectateMode
@ -156,6 +195,10 @@ module.exports = class LevelLoader extends CocoClass
buildSpriteSheetsForThangType: (thangType) ->
return if @headless
# TODO: Finish making sure the supermodel loads the raster image before triggering load complete, and that the cocosprite has access to the asset.
# if f = thangType.get('raster')
# queue = new createjs.LoadQueue()
# queue.loadFile('/file/'+f)
@grabThangTypeTeams() unless @thangTypeTeams
for team in @thangTypeTeams[thangType.get('original')] ? [null]
spriteOptions = {resolutionFactor: 4, async: false}
@ -198,6 +241,7 @@ module.exports = class LevelLoader extends CocoClass
@world = new World()
serializedLevel = @level.serialize(@supermodel)
@world.loadFromLevel serializedLevel, false
console.log "World has been initialized from level loader."
# Initial Sound Loading
@ -223,3 +267,7 @@ module.exports = class LevelLoader extends CocoClass
# everything else sound wise is loaded as needed as worlds are generated
progress: -> @supermodel.progress
destroy: ->
clearInterval @buildLoopInterval if @buildLoopInterval
super()

View file

@ -74,7 +74,7 @@ module.exports = class Simulator extends CocoClass
return
@supermodel ?= new SuperModel()
@supermodel.resetProgress()
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), headless: true
if @supermodel.finished()
@simulateGame()

View file

@ -70,7 +70,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@age = 0
@scaleFactor = @targetScaleFactor = 1
@displayObject = new createjs.Container()
if @thangType.get('actions')
if @thangType.isFullyLoaded()
@setupSprite()
else
@stillLoading = true
@ -79,9 +79,29 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
setupSprite: ->
@stillLoading = false
@actions = @thangType.getActions()
@buildFromSpriteSheet @buildSpriteSheet()
@createMarks()
if @thangType.get('raster')
@isRaster = true
@setUpRasterImage()
@actions = {}
else
@actions = @thangType.getActions()
@buildFromSpriteSheet @buildSpriteSheet()
@createMarks()
setUpRasterImage: ->
raster = @thangType.get('raster')
sprite = @imageObject = new createjs.Bitmap('/file/'+raster)
@displayObject.addChild(sprite)
@configureMouse()
@originalScaleX = sprite.scaleX
@originalScaleY = sprite.scaleY
@displayObject.sprite = @
@displayObject.layerPriority = @thangType.get 'layerPriority'
@displayObject.name = @thang?.spriteName or @thangType.get 'name'
reg = @getOffset 'registration'
@imageObject.regX = -reg.x
@imageObject.regY = -reg.y
@updateScale()
destroy: ->
mark.destroy() for name, mark of @marks
@ -126,6 +146,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
queueAction: (action) ->
# The normal way to have an action play
return unless @thangType.isFullyLoaded()
action = @actions[action] if _.isString(action)
action ?= @actions.idle
@actionQueue = []
@ -143,6 +164,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@playAction(@actionQueue.splice(0,1)[0]) if @actionQueue.length
playAction: (action) ->
return if @isRaster
@currentAction = action
return @hide() unless action.animation or action.container or action.relatedActions
@show()
@ -245,15 +267,17 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@hasMoved = true
updateScale: ->
return unless @imageObject
if @thangType.get('matchWorldDimensions') and @thang
if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight
[@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height]
bounds = @imageObject.getBounds()
return unless bounds # TODO: remove this because it's a bandaid over the image sometimes not being loaded
@imageObject.scaleX = @thang.width * Camera.PPM / bounds.width
@imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height
unless @thang.spriteName is 'Beam'
@imageObject.scaleX *= @thangType.get('scale') ? 1
@imageObject.scaleY *= @thangType.get('scale') ? 1
[@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height]
return
scaleX = if @getActionProp 'flipX' then -1 else 1
scaleY = if @getActionProp 'flipY' then -1 else 1
@ -270,6 +294,12 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
angle = -angle if angle < 0
angle = 180 - angle if angle > 90
scaleX = 0.5 + 0.5 * (90 - angle) / 90
if @isRaster # scale is worked into building the sprite sheet for animations
scale = @thangType.get('scale') or 1
scaleX *= scale
scaleY *= scale
scaleFactorX = @thang.scaleFactorX ? @scaleFactor
scaleFactorY = @thang.scaleFactorY ? @scaleFactor
@imageObject.scaleX = @originalScaleX * scaleX * scaleFactorX
@ -322,6 +352,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
##################################################
updateAction: ->
return if @isRaster
action = @determineAction()
isDifferent = action isnt @currentRootAction or action is null
if not action and @thang?.actionActivated and not @stopLogging
@ -443,10 +474,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
def = x: 0, y: {registration: 0, torso: -50, mouth: -60, aboveHead: -100}[prop]
pos = @getActionProp 'positions', prop, def
pos = x: pos.x, y: pos.y
scale = @getActionProp 'scale', null, 1
scale *= @options.resolutionFactor if prop is 'registration'
pos.x *= scale
pos.y *= scale
if not @isRaster
scale = @getActionProp 'scale', null, 1
scale *= @options.resolutionFactor if prop is 'registration'
pos.x *= scale
pos.y *= scale
if @thang and prop isnt 'registration'
scaleFactor = @thang.scaleFactor ? 1
pos.x *= @thang.scaleFactorX ? scaleFactor
@ -658,7 +690,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
z = @shadow.pos.z
@shadow.pos = pos
@shadow.pos.z = z
@imageObject.gotoAndPlay(endAnimation)
@imageObject.gotoAndPlay?(endAnimation)
return
@shadow.action = 'move'

View file

@ -101,4 +101,5 @@ module.exports = class Layer extends createjs.Container
cache: ->
return unless @children.length
bounds = @getBounds()
return unless bounds
super bounds.x, bounds.y, bounds.width, bounds.height, 2

View file

@ -200,7 +200,7 @@ module.exports = class Mark extends CocoClass
Backbone.Mediator.publish 'sprite:loaded'
update: (pos=null) ->
return false unless @on and @mark
return false unless @on and @mark and @sprite?.thangType.isFullyLoaded()
@mark.visible = not @hidden
@updatePosition pos
@updateRotation()
@ -242,7 +242,7 @@ module.exports = class Mark extends CocoClass
oldMark.parent.removeChild oldMark
return unless @name in ["selection", "target", "repair", "highlight"]
scale = 0.5
if @sprite
if @sprite?.imageObject
size = @sprite.getAverageDimension()
size += 60 if @name is 'selection'
size += 60 if @name is 'repair'

View file

@ -47,7 +47,7 @@ module.exports = class SpriteBoss extends CocoClass
toString: -> "<SpriteBoss: #{@spriteArray.length} sprites>"
thangTypeFor: (type) ->
_.find @options.thangTypes, (m) -> m.get('original') is type or m.get('name') is type
_.find @options.thangTypes, (m) -> m.get('actions') and m.get('original') is type or m.get('name') is type
createLayers: ->
@spriteLayers = {}
@ -144,7 +144,11 @@ module.exports = class SpriteBoss extends CocoClass
addThangToSprites: (thang, layer=null) ->
return console.warn 'Tried to add Thang to the surface it already has:', thang.id if @sprites[thang.id]
thangType = _.find @options.thangTypes, (m) -> m.get('name') is thang.spriteName
thangType = _.find @options.thangTypes, (m) ->
return false unless m.get('actions') or m.get('raster')
return m.get('name') is thang.spriteName
thangType ?= _.find @options.thangTypes, (m) -> return m.get('name') is thang.spriteName
options = @createSpriteOptions thang: thang
options.resolutionFactor = if thangType.get('kind') is 'Floor' then 2 else 4
sprite = new CocoSprite thangType, options
@ -196,6 +200,8 @@ module.exports = class SpriteBoss extends CocoClass
cache: (update=false) ->
return if @cached and not update
wallSprites = (sprite for sprite in @spriteArray when sprite.thangType?.get('name').search(/(dungeon|indoor).wall/i) isnt -1)
unless _.all (s.thangType.isFullyLoaded() for s in wallSprites)
return
walls = (sprite.thang for sprite in wallSprites)
@world.calculateBounds()
wallGrid = new Grid walls, @world.size()...

View file

@ -84,6 +84,8 @@ module.exports = Surface = class Surface extends CocoClass
@initAudio()
@onResize = _.debounce @onResize, 500
$(window).on 'resize', @onResize
if @world.ended
_.defer => @setWorld @world
destroy: ->
@dead = true

View file

@ -699,6 +699,7 @@
user_schema: "User Schema"
user_profile: "User Profile"
patches: "Patches"
patched_model: "Source Document"
model: "Model"
system: "System"
component: "Component"
@ -709,10 +710,12 @@
opponent_session: "Opponent Session"
article: "Article"
user_names: "User Names"
thang_names: "Thang Names"
files: "Files"
top_simulators: "Top Simulators"
source_document: "Source Document"
document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database
sprite_sheet: "Sprite Sheet"
delta:
added: "Added"

View file

@ -37,6 +37,8 @@ class CocoModel extends Backbone.Model
@loading = false
@markToRevert()
@loadFromBackup()
getNormalizedURL: -> "#{@urlRoot}/#{@id}"
set: ->
res = super(arguments...)
@ -76,9 +78,9 @@ class CocoModel extends Backbone.Model
return super attrs, options
fetch: ->
res = super(arguments...)
@jqxhr = super(arguments...)
@loading = true
res
@jqxhr
markToRevert: ->
if @type() is 'ThangType'

View file

@ -5,6 +5,7 @@ module.exports = class SuperModel extends Backbone.Model
@progress = 0
@resources = {}
@rid = 0
@maxProgress = 1
@models = {}
@collections = {}
@ -19,7 +20,6 @@ module.exports = class SuperModel extends Backbone.Model
loadModel: (model, name, fetchOptions, value=1) ->
cachedModel = @getModelByURL(model.getURL())
if cachedModel
console.debug 'Model cache hit', cachedModel.getURL(), 'already loaded', cachedModel.loaded
if cachedModel.loaded
res = @addModelResource(cachedModel, name, fetchOptions, 0)
res.markLoaded()
@ -96,7 +96,7 @@ module.exports = class SuperModel extends Backbone.Model
@registerCollection(collection)
registerCollection: (collection) ->
@collections[collection.getURL()] = collection
@collections[collection.getURL()] = collection if collection.isCachable
# consolidate models
for model, i in collection.models
cachedModel = @getModelByURL(model.getURL())
@ -141,7 +141,7 @@ module.exports = class SuperModel extends Backbone.Model
@listenToOnce(resource, 'loaded', @onResourceLoaded)
@listenTo(resource, 'failed', @onResourceFailed)
@denom += value
@updateProgress() if @denom
_.defer @updateProgress if @denom
onResourceLoaded: (r) ->
@num += r.value
@ -155,11 +155,18 @@ module.exports = class SuperModel extends Backbone.Model
# a bunch of things load all at once.
# So make sure we only emit events if @progress has changed.
newProg = if @denom then @num / @denom else 1
return if @progress is newProg
newProg = Math.min @maxProgress, newProg
return if @progress >= newProg
@progress = newProg
@trigger('update-progress', @progress)
@trigger('loaded-all') if @finished()
setMaxProgress: (@maxProgress) ->
resetProgress: -> @progress = 0
clearMaxProgress: ->
@maxProgress = 1
_.defer @updateProgress
getProgress: -> return @progress
getResource: (rid) ->
@ -202,6 +209,7 @@ class ModelResource extends Resource
super(name, value)
@model = modelOrCollection
@fetchOptions = fetchOptions
@jqxhr = @model.jqxhr
load: ->
@markLoading()

View file

@ -26,12 +26,18 @@ module.exports = class ThangType extends CocoModel
@buildActions()
@spriteSheets = {}
@building = {}
isFullyLoaded: ->
# TODO: Come up with a better way to identify when the model doesn't have everything needed to build the sprite. ie when it's a projection without all the required data.
return @get('actions') or @get('raster') # needs one of these two things
getActions: ->
return {} unless @isFullyLoaded()
return @actions or @buildActions()
buildActions: ->
@actions = $.extend(true, {}, @get('actions') or {})
return null unless @isFullyLoaded()
@actions = $.extend(true, {}, @get('actions'))
for name, action of @actions
action.name = name
for relatedName, relatedAction of action.relatedActions ? {}
@ -52,9 +58,12 @@ module.exports = class ThangType extends CocoModel
options
buildSpriteSheet: (options) ->
return false unless @isFullyLoaded()
@options = @fillOptions options
key = @spriteSheetKey(@options)
if ss = @spriteSheets[key] then return ss
return if @building[key]
@t0 = new Date().getTime()
@initBuild(options)
@addGeneralFrames() unless @options.portraitOnly
@addPortrait()
@ -144,9 +153,8 @@ module.exports = class ThangType extends CocoModel
@builder.buildAsync() unless buildQueue.length > 1
@builder.on 'complete', @onBuildSpriteSheetComplete, @, true, key
return true
t0 = new Date()
spriteSheet = @builder.build()
console.warn "Built #{@get('name')} in #{new Date() - t0}ms on main thread."
console.debug "Built #{@get('name')} in #{new Date().getTime() - @t0}ms."
@spriteSheets[key] = spriteSheet
delete @building[key]
spriteSheet
@ -180,6 +188,7 @@ module.exports = class ThangType extends CocoModel
stage?.toDataURL()
getPortraitStage: (spriteOptionsOrKey, size=100) ->
return unless @isFullyLoaded()
key = spriteOptionsOrKey
key = if _.isString(key) then key else @spriteSheetKey(@fillOptions(key))
spriteSheet = @spriteSheets[key]
@ -210,8 +219,8 @@ module.exports = class ThangType extends CocoModel
@tick = null
stage
uploadGenericPortrait: (callback) ->
src = @getPortraitSource()
uploadGenericPortrait: (callback, src) ->
src ?= @getPortraitSource()
return callback?() unless src
src = src.replace('data:image/png;base64,', '').replace(/\ /g, '+')
body =

View file

@ -123,6 +123,7 @@ _.extend ThangTypeSchema.properties,
title: 'Scale'
type: 'number'
positions: PositionsSchema
raster: { type: 'string', format: 'image-file', title: 'Raster Image' }
colorGroups: c.object
title: 'Color Groups'
additionalProperties:

View file

@ -1,3 +1,6 @@
.patches-view
.status-buttons
margin-bottom: 10px
.patch-icon
cursor: pointer

View file

@ -8,7 +8,7 @@ module.exports = class PatchModal extends ModalView
template: template
plain: true
modalWidthPercent: 60
events:
'click #withdraw-button': 'withdrawPatch'
'click #reject-button': 'rejectPatch'
@ -22,7 +22,7 @@ module.exports = class PatchModal extends ModalView
else
@originalSource = new @targetModel.constructor({_id:targetID})
@supermodel.loadModel @originalSource, 'source_document'
getRenderData: ->
c = super()
c.isPatchCreator = @patch.get('creator') is auth.me.id
@ -30,7 +30,7 @@ module.exports = class PatchModal extends ModalView
c.status = @patch.get 'status'
c.patch = @patch
c
afterRender: ->
return unless @supermodel.finished()
headModel = null
@ -38,7 +38,7 @@ module.exports = class PatchModal extends ModalView
headModel = @originalSource.clone(false)
headModel.set(@targetModel.attributes)
headModel.loaded = true
pendingModel = @originalSource.clone(false)
pendingModel.applyDelta(@patch.get('delta'))
pendingModel.loaded = true
@ -47,18 +47,18 @@ module.exports = class PatchModal extends ModalView
changeEl = @$el.find('.changes-stub')
@insertSubView(@deltaView, changeEl)
super()
acceptPatch: ->
delta = @deltaView.getApplicableDelta()
@targetModel.applyDelta(delta)
@patch.setStatus('accepted')
@trigger 'accepted-patch'
@hide()
rejectPatch: ->
@patch.setStatus('rejected')
@hide()
withdrawPatch: ->
@patch.setStatus('withdrawn')
@hide()
@hide()

View file

@ -8,7 +8,7 @@ module.exports = class PatchesView extends CocoView
template: template
className: 'patches-view'
status: 'pending'
events:
'change .status-buttons': 'onStatusButtonsChanged'
'click .patch-icon': 'openPatchModal'
@ -16,16 +16,16 @@ module.exports = class PatchesView extends CocoView
constructor: (@model, options) ->
super(options)
@initPatches()
initPatches: ->
@startedLoading = false
@patches = new PatchesCollection([], {}, @model, @status)
load: ->
@initPatches()
@patches = @supermodel.loadCollection(@patches, 'patches').model
@listenTo @patches, 'sync', @onPatchesLoaded
onPatchesLoaded: ->
ids = (p.get('creator') for p in @patches.models)
jqxhrOptions = nameLoader.loadNames ids
@ -37,19 +37,20 @@ module.exports = class PatchesView extends CocoView
c.patches = @patches.models
c.status
c
afterRender: ->
@$el.find(".#{@status}").addClass 'active'
onStatusButtonsChanged: (e) ->
@status = $(e.target).val()
@reloadPatches()
reloadPatches: ->
@load()
@render()
openPatchModal: (e) ->
console.log "open patch modal"
patch = _.find @patches.models, {id:$(e.target).data('patch-id')}
modal = new PatchModal(patch, @model)
@openModalView(modal)

View file

@ -197,6 +197,7 @@ module.exports = class ThangTypeEditView extends View
# animation select
refreshAnimation: ->
return @showRasterImage() if @thangType.get('raster')
options = @getSpriteOptions()
@thangType.resetSpriteSheetCache()
spriteSheet = @thangType.buildSpriteSheet(options)
@ -207,6 +208,13 @@ module.exports = class ThangTypeEditView extends View
@showAnimation()
@updatePortrait()
showRasterImage: ->
sprite = new CocoSprite(@thangType, @getSpriteOptions())
@currentSprite?.destroy()
@currentSprite = sprite
@showDisplayObject(sprite.displayObject)
@updateScale()
showAnimation: (animationName) ->
animationName = @$el.find('#animations-select').val() unless _.isString animationName
return unless animationName
@ -310,8 +318,13 @@ module.exports = class ThangTypeEditView extends View
res.success =>
url = "/editor/thang/#{newThangType.get('slug') or newThangType.id}"
newThangType.uploadGenericPortrait ->
document.location.href = url
portraitSource = null
if @thangType.get('raster')
image = @currentSprite.imageObject.image
portraitSource = imageToPortrait image
# bit of a hacky way to get that portrait
success = -> document.location.href = url
newThangType.uploadGenericPortrait success, portraitSource
clearRawData: ->
@thangType.resetRawData()
@ -393,3 +406,14 @@ module.exports = class ThangTypeEditView extends View
destroy: ->
@camera?.destroy()
super()
imageToPortrait = (img) ->
canvas = document.createElement("canvas")
canvas.width = 100
canvas.height = 100
ctx = canvas.getContext("2d")
scaleX = 100 / img.width
scaleY = 100 / img.height
ctx.scale scaleX, scaleY
ctx.drawImage img, 0, 0
canvas.toDataURL("image/png")

View file

@ -109,19 +109,29 @@ module.exports = class HUDView extends View
@update()
createAvatar: (thangType, thang, colorConfig) ->
unless thangType.isFullyLoaded()
args = arguments
unless @listeningToCreateAvatar
@listenToOnce thangType, 'sync', -> @createAvatar(args...)
@listeningToCreateAvatar = true
return
@listeningToCreateAvatar = false
options = thang.getSpriteOptions() or {}
options.async = false
options.colorConfig = colorConfig if colorConfig
stage = thangType.getPortraitStage options
wrapper = @$el.find '.thang-canvas-wrapper'
newCanvas = $(stage.canvas).addClass('thang-canvas')
wrapper.empty().append(newCanvas)
team = @thang?.team or @speakerSprite?.thang?.team
wrapper.removeClass (i, css) -> (css.match(/\bteam-\S+/g) or []).join ' '
wrapper.addClass "team-#{team}"
stage.update()
@stage?.stopTalking()
@stage = stage
if thangType.get('raster')
wrapper.empty().append($('<img />').attr('src', '/file/'+thangType.get('raster')))
else
stage = thangType.getPortraitStage options
newCanvas = $(stage.canvas).addClass('thang-canvas')
wrapper.empty().append(newCanvas)
stage.update()
@stage?.stopTalking()
@stage = stage
onThangBeganTalking: (e) ->
return unless @stage and @thang is e.thang

View file

@ -14,16 +14,28 @@ module.exports = class ThangAvatarView extends View
super options
@thang = options.thang
@includeName = options.includeName
@thangType = @getSpriteThangType()
if not @thangType
console.error 'Thang avatar view expected a thang type to be provided.'
return
unless @thangType.isFullyLoaded() or @thangType.loading
@thangType.fetch()
@supermodel.loadModel @thangType, 'thang'
getSpriteThangType: ->
thangs = @supermodel.getModels(ThangType)
thangs = (t for t in thangs when t.get('name') is @thang.spriteName)
loadedThangs = (t for t in thangs when t.isFullyLoaded())
return loadedThangs[0] or thangs[0] # try to return one with all the goods, otherwise a projection
getRenderData: (context={}) ->
context = super context
context.thang = @thang
thangs = @supermodel.getModels(ThangType)
thangs = (t for t in thangs when t.get('name') is @thang.spriteName)
thang = thangs[0]
options = @thang?.getSpriteOptions() or {}
options.async = false
context.avatarURL = thang.getPortraitSource(options)
context.avatarURL = @thangType.getPortraitSource(options) unless @thangType.loading
context.includeName = @includeName
context

View file

@ -67,7 +67,7 @@ module.exports = class SpellView extends View
@createFirepad()
else
# needs to happen after the code generating this view is complete
setTimeout @onAllLoaded, 1
_.defer @onAllLoaded
createACE: ->
# Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html

View file

@ -60,7 +60,6 @@ module.exports = class PlayLevelView extends View
'surface:world-set-up': 'onSurfaceSetUpNewWorld'
'level:session-will-save': 'onSessionWillSave'
'level:set-team': 'setTeam'
'god:new-world-created': 'loadSoundsForWorld'
'level:started': 'onLevelStarted'
'level:loading-view-unveiled': 'onLoadingViewUnveiled'
@ -83,7 +82,6 @@ module.exports = class PlayLevelView extends View
@sessionID = @getQueryVariable 'session'
$(window).on('resize', @onWindowResize)
@listenToOnce(@supermodel, 'error', @onLevelLoadError)
@saveScreenshot = _.throttle @saveScreenshot, 30000
if @isEditorPreview
@ -102,7 +100,6 @@ module.exports = class PlayLevelView extends View
@supermodel.models = givenSupermodel.models
@supermodel.collections = givenSupermodel.collections
@supermodel.shouldSaveBackups = givenSupermodel.shouldSaveBackups
@god?.level = @level.serialize @supermodel
if @world
serializedLevel = @level.serialize(@supermodel)
@ -133,6 +130,9 @@ module.exports = class PlayLevelView extends View
updateProgress: (progress) ->
super(progress)
if not @worldInitialized and @levelLoader.session.loaded and @levelLoader.level.loaded and @levelLoader.world and (not @levelLoader.opponentSession or @levelLoader.opponentSession.loaded)
@grabLevelLoaderData()
@onWorldInitialized()
return if @seenDocs
return unless @levelLoader.session.loaded and @levelLoader.level.loaded
return unless showFrequency = @levelLoader.level.get('showsGuide')
@ -144,6 +144,21 @@ module.exports = class PlayLevelView extends View
return unless article.loaded
@showGuide()
onWorldInitialized: ->
@worldInitialized = true
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
@loadOpponentTeam(team)
@god.setLevel @level.serialize @supermodel
@god.setWorldClassMap @world.classMap
@setTeam team
@initGoalManager()
@insertSubviews ladderGame: (@level.get('type') is "ladder")
@initVolume()
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@originalSessionState = $.extend(true, {}, @session.get('state'))
@register()
@controlBar.setBus(@bus)
showGuide: ->
@seenDocs = true
DocsModal = require './level/modal/docs_modal'
@ -165,33 +180,16 @@ module.exports = class PlayLevelView extends View
if not (@levelLoader.level.get('type') in ['ladder', 'ladder-tutorial'])
me.set('lastLevel', @levelID)
me.save()
@grabLevelLoaderData()
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
@loadOpponentTeam(team)
@god.setLevel @level.serialize @supermodel
@god.setWorldClassMap @world.classMap
@setTeam team
@levelLoader.destroy()
@levelLoader = null
@initSurface()
@initGoalManager()
@initScriptManager()
@insertSubviews()
@initVolume()
@listenTo(@session, 'change:multiplayer', @onMultiplayerChanged)
@originalSessionState = $.extend(true, {}, @session.get('state'))
@register()
@controlBar.setBus(@bus)
@surface.showLevel()
if @otherSession
# TODO: colorize name and cloud by team, colorize wizard by user's color config
@surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team')
grabLevelLoaderData: ->
@session = @levelLoader.session
@world = @levelLoader.world
@level = @levelLoader.level
@otherSession = @levelLoader.opponentSession
@levelLoader.destroy()
@levelLoader = null
loadOpponentTeam: (myTeam) ->
opponentSpells = []
@ -212,6 +210,10 @@ module.exports = class PlayLevelView extends View
@session.set 'multiplayer', false
onLevelStarted: (e) ->
@surface.showLevel()
if @otherSession
# TODO: colorize name and cloud by team, colorize wizard by user's color config
@surface.createOpponentWizard id: @otherSession.get('creator'), name: @otherSession.get('creatorName'), team: @otherSession.get('team')
@loadingView?.unveil()
onLoadingViewUnveiled: (e) ->
@ -306,9 +308,6 @@ module.exports = class PlayLevelView extends View
$('#level-done-button', @$el).hide()
application.tracker?.trackEvent 'Confirmed Restart', level: @world.name, label: @world.name
onNewWorld: (e) ->
@world = e.world
onInfiniteLoop: (e) ->
return unless e.firstWorld
@openModalView new InfiniteLoopModal()
@ -481,11 +480,11 @@ module.exports = class PlayLevelView extends View
# Dynamic sound loading
loadSoundsForWorld: (e) ->
onNewWorld: (e) ->
return if @headless
world = e.world
@world = e.world
thangTypes = @supermodel.getModels(ThangType)
for [spriteName, message] in world.thangDialogueSounds()
for [spriteName, message] in @world.thangDialogueSounds()
continue unless thangType = _.find thangTypes, (m) -> m.get('name') is spriteName
continue unless sound = AudioPlayer.soundForDialogue message, thangType.get('soundTriggers')
AudioPlayer.preloadSoundReference sound

View file

@ -112,7 +112,7 @@ module.exports = class Handler
ids = ids.split(',') if _.isString ids
ids = _.uniq ids
project = {name:1}
project = {name:1, original:1}
sort = {'version.major':-1, 'version.minor':-1}
makeFunc = (id) =>
@ -120,8 +120,8 @@ module.exports = class Handler
criteria = {original:mongoose.Types.ObjectId(id)}
@modelClass.findOne(criteria, project).sort(sort).exec (err, document) ->
return done(err) if err
callback(null, document?.toObject() or {})
callback(null, document?.toObject() or null)
funcs = {}
for id in ids
return errors.badInput(res, "Given an invalid id: #{id}") unless Handler.isID(id)
@ -129,7 +129,7 @@ module.exports = class Handler
async.parallel funcs, (err, results) ->
return errors.serverError err if err
res.send results
res.send (d for d in _.values(results) when d)
res.end()
getPatchesFor: (req, res, id) ->

View file

@ -5,21 +5,22 @@ ThangTypeHandler = class ThangTypeHandler extends Handler
modelClass: ThangType
jsonSchema: require '../../../app/schemas/models/thang_type'
editableProperties: [
'name',
'raw',
'actions',
'soundTriggers',
'rotationType',
'matchWorldDimensions',
'shadow',
'layerPriority',
'staticImage',
'scale',
'positions',
'snap',
'components',
'colorGroups',
'name'
'raw'
'actions'
'soundTriggers'
'rotationType'
'matchWorldDimensions'
'shadow'
'layerPriority'
'staticImage'
'scale'
'positions'
'snap'
'components'
'colorGroups'
'kind'
'raster'
]
hasAccess: (req) ->