This commit is contained in:
Nick Winter 2014-05-14 15:31:19 -07:00
commit d61922ae42
33 changed files with 366 additions and 144 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()
@ -269,7 +269,8 @@ module.exports = class Simulator extends CocoClass
if spellTeam not in playerTeams then useProtectAPI = false
@spells[spellKey].thangs[thang.id].aether = @createAether @spells[spellKey].name, method, useProtectAPI
transpileSpell: (thang, spellKey, methodName) ->
transpileSpell: (thang, spellKey, methodName) ->
slugifiedThangID = _.string.slugify thang.id
source = @currentUserCodeMap[[slugifiedThangID,methodName].join '/'] ? ""
aether = @spells[spellKey].thangs[thang.id].aether
@ -284,7 +285,7 @@ module.exports = class Simulator extends CocoClass
functionName: methodName
protectAPI: useProtectAPI
includeFlow: false
yieldConditionally: false
yieldConditionally: methodName is "plan"
globals: ['Vector', '_']
problems:
jshint_W040: {level: "ignore"}

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,30 @@ 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)
$(sprite.image).one 'load', => @updateScale?()
@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 +147,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 +165,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 +268,22 @@ 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
@imageObject.scaleX = @thang.width * Camera.PPM / bounds.width
@imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height
@imageObject.regX ?= 0
@imageObject.regX += bounds.width / 2
@imageObject.regY ?= 0
@imageObject.regY += bounds.height / 2
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 +300,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 +358,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 +480,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 +696,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

@ -213,6 +213,9 @@ module.exports = class GoalManager extends CocoClass
else
state[progressObjectName][thang] = false
getGoalState: (goalID) ->
@goalStates[goalID].status
setGoalState: (goalID, status) ->
state = @goalStates[goalID]
state.status = status

View file

@ -38,6 +38,9 @@ module.exports = class Thang
event.thang = @
@world.publishNote channel, event
getGoalState: (goalID) ->
@world.getGoalState goalID
setGoalState: (goalID, status) ->
@world.setGoalState goalID, status

View file

@ -242,6 +242,9 @@ module.exports = class World
return unless @goalManager
@goalManager.submitWorldGenerationEvent(channel, event, @frames.length)
getGoalState: (goalID) ->
@goalManager.getGoalState(goalID)
setGoalState: (goalID, status) ->
@goalManager.setGoalState(goalID, status)

View file

@ -120,7 +120,7 @@
forum_suffix: " instead."
send: "Send Feedback"
contact_candidate: "Contact Candidate"
recruitment_reminder: "Use this form to reach out to candidates you are interested in interviewing. Remember that CodeCombat charges 18% of first-year salary. The fee is due upon hiring the employee and is refundable for 90 days if the employee does not remain employed. Part time, remote, and contract employees are free, as are interns."
recruitment_reminder: "Use this form to reach out to candidates you are interested in interviewing. Remember that CodeCombat charges 15% of first-year salary. The fee is due upon hiring the employee and is refundable for 90 days if the employee does not remain employed. Part time, remote, and contract employees are free, as are interns."
diplomat_suggestion:
title: "Help translate CodeCombat!"
@ -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

@ -4,7 +4,7 @@ block modal-header-content
h3(data-i18n="contact.contact_candidate") Contact Candidate
block modal-body-content
p(data-i18n="contact.recruitment_reminder") Use this form to reach out to candidates you are interested in interviewing. Remember that CodeCombat charges 18% of first-year salary. The fee is due upon hiring the employee and is refundable for 90 days if the employee does not remain employed. Part time, remote, and contract employees are free, as are interns.
p(data-i18n="contact.recruitment_reminder") Use this form to reach out to candidates you are interested in interviewing. Remember that CodeCombat charges 15% of first-year salary. The fee is due upon hiring the employee and is refundable for 90 days if the employee does not remain employed. Part time, remote, and contract employees are free, as are interns.
.form
.form-group
label.control-label(for="contact-email", data-i18n="general.email") Email

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

@ -185,6 +185,6 @@ module.exports = class EmployersView extends View
else
window.location.hash = id
url = "/account/profile/#{id}"
app.router.navigate url, {trigger: true}
window.open url,"_blank"
else
@openModalView new EmployerSignupView

View file

@ -182,8 +182,15 @@ module.exports = class MyMatchesTabView extends CocoView
failure = (jqxhr, textStatus, errorThrown) =>
console.log jqxhr.responseText
@setRankingButtonText(button, 'failed')
transpiledCode = @transpileSession session
ajaxData =
session: sessionID
levelID: @level.id
originalLevelID: @level.attributes.original
levelMajorVersion: @level.attributes.version.major
transpiledCode: transpiledCode
ajaxData = {session: sessionID, levelID: @level.id, originalLevelID: @level.attributes.original, levelMajorVersion: @level.attributes.version.major}
$.ajax '/queue/scoring', {
type: 'POST'
data: ajaxData
@ -191,6 +198,28 @@ module.exports = class MyMatchesTabView extends CocoView
error: failure
}
transpileSession: (session) ->
submittedCode = session.get('code')
transpiledCode = {}
for thang, spells of submittedCode
transpiledCode[thang] = {}
for spellID, spell of spells
#DRY this
aetherOptions =
problems: {}
language: "javascript"
functionName: spellID
functionParameters: []
yieldConditionally: spellID is "plan"
globals: ['Vector', '_']
protectAPI: true
includeFlow: false
if spellID is "hear" then aetherOptions["functionParameters"] = ["speaker","message","data"]
aether = new Aether aetherOptions
transpiledCode[thang][spellID] = aether.transpile spell
transpiledCode
setRankingButtonText: (rankButton, spanClass) ->
rankButton.find('span').addClass('hidden')
rankButton.find(".#{spanClass}").removeClass('hidden')

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

@ -13,18 +13,16 @@ Aether.addGlobal 'Vector', require '../app/lib/world/vector'
Aether.addGlobal '_', _
transpileLevelSession = (sessionID, cb) ->
query = LevelSession
.findOne("_id": sessionID)
.select("submittedCode")
.lean()
query = LevelSession.findOne("_id": sessionID).select("submittedCode").lean()
query.exec (err, session) ->
if err then return cb err
submittedCode = session.submittedCode
transpiledCode = {}
console.log "Updating session #{sessionID}"
for thang, spells of submittedCode
transpiledCode[thang] = {}
for spellID, spell of spells
aetherOptions =
problems: {}
language: "javascript"
@ -34,15 +32,18 @@ transpileLevelSession = (sessionID, cb) ->
globals: ['Vector', '_']
protectAPI: true
includeFlow: false
if spellID is "hear" then aetherOptions["functionParameters"] = ["speaker","message","data"]
aether = new Aether aetherOptions
transpiledCode[thang][spellID] = aether.transpile spell
cb null
conditions =
"_id": sessionID
update =
"transpiledCode"
query = LevelSession
.update("_id")
"transpiledCode": transpiledCode
"submittedCodeLanguage": "javascript"
query = LevelSession.update(conditions,update)
query.exec (err, numUpdated) -> cb err
findLadderLevelSessions = (levelID, cb) ->
queryParameters =
@ -50,18 +51,14 @@ findLadderLevelSessions = (levelID, cb) ->
submitted: true
selectString = "_id"
query = LevelSession
.find(queryParameters)
.select(selectString)
.lean()
query = LevelSession.find(queryParameters).select(selectString).lean()
query.exec (err, levelSessions) ->
if err then return cb err
levelSessionIDs = _.pluck levelSessions, "_id"
transpileLevelSession levelSessionIDs[0], (err) ->
throw err if err
#async.each levelSessionIDs, transpileLevelSession, (err) ->
# if err then return cb err
# cb null
async.eachSeries levelSessionIDs, transpileLevelSession, (err) ->
if err then return cb err
cb null
transpileLadderSessions = ->
@ -70,17 +67,14 @@ transpileLadderSessions = ->
"version.isLatestMajor": true
"version.isLatestMinor": true
selectString = "original"
query = Level
.find(queryParameters)
.select(selectString)
.lean()
query = Level.find(queryParameters).select(selectString).lean()
query.exec (err, ladderLevels) ->
throw err if err
ladderLevels = _.pluck ladderLevels, "original"
findLadderLevelSessions ladderLevels[3], (err) ->
async.eachSeries ladderLevels, findLadderLevelSessions, (err) ->
throw err if err
#async.each ladderLevels, findLadderLevelSessions, (err) ->
# throw err if err
serverSetup.connectToDatabase()
transpileLadderSessions()

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) ->

View file

@ -104,13 +104,14 @@ module.exports.createNewTask = (req, res) ->
requestSessionID = req.body.session
originalLevelID = req.body.originalLevelID
currentLevelID = req.body.levelID
transpiledCode = req.body.transpiledCode
requestLevelMajorVersion = parseInt(req.body.levelMajorVersion)
async.waterfall [
validatePermissions.bind(@,req,requestSessionID)
fetchAndVerifyLevelType.bind(@,currentLevelID)
fetchSessionObjectToSubmit.bind(@, requestSessionID)
updateSessionToSubmit
updateSessionToSubmit.bind(@, transpiledCode)
fetchInitialSessionsToRankAgainst.bind(@, requestLevelMajorVersion, originalLevelID)
generateAndSendTaskPairsToTheQueue
], (err, successMessageObject) ->
@ -163,10 +164,11 @@ fetchSessionObjectToSubmit = (sessionID, callback) ->
query.exec (err, session) ->
callback err, session?.toObject()
updateSessionToSubmit = (sessionToUpdate, callback) ->
updateSessionToSubmit = (transpiledCode, sessionToUpdate, callback) ->
sessionUpdateObject =
submitted: true
submittedCode: sessionToUpdate.code
transpiledCode: transpiledCode
submitDate: new Date()
meanStrength: 25
standardDeviation: 25/3