mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-24 21:13:35 -04:00
Merge branch 'feature/thangload'
This commit is contained in:
commit
7a381df348
25 changed files with 292 additions and 111 deletions
app
assets/javascripts/workers
collections
lib
locale
models
schemas/models
styles/editor
views
server
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
14
app/collections/ThangNamesCollection.coffee
Normal file
14
app/collections/ThangNamesCollection.coffee
Normal 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)
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()...
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
.patches-view
|
||||
.status-buttons
|
||||
margin-bottom: 10px
|
||||
|
||||
.patch-icon
|
||||
cursor: pointer
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) ->
|
||||
|
|
|
@ -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) ->
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue