mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2025-04-26 05:53:39 -04:00
Merge branch 'master' of https://github.com/codecombat/codecombat
This commit is contained in:
commit
a2fbe5865a
35 changed files with 562 additions and 278 deletions
app
assets/javascripts/workers
initialize.coffeelib
locale
models
schemas
templates/play/ladder
views
server/queues
server_setup.coffee
|
@ -378,6 +378,7 @@ self.onWorldLoaded = function onWorldLoaded() {
|
|||
}
|
||||
var t3 = new Date();
|
||||
console.log("And it was so: (" + (diff / self.world.totalFrames).toFixed(3) + "ms per frame,", self.world.totalFrames, "frames)\nSimulation :", diff + "ms \nSerialization:", (t2 - t1) + "ms\nDelivery :", (t3 - t2) + "ms");
|
||||
self.world.goalManager.destroy();
|
||||
self.world = null;
|
||||
};
|
||||
|
||||
|
@ -408,6 +409,7 @@ self.onWorldLoadProgress = function onWorldLoadProgress(progress) {
|
|||
self.abort = function abort() {
|
||||
if(self.world) {
|
||||
self.world.abort();
|
||||
self.world.goalManager.destroy();
|
||||
self.world = null;
|
||||
}
|
||||
self.postMessage({type: 'abort'});
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
Backbone.Mediator.setValidationEnabled false
|
||||
app = require 'application'
|
||||
|
||||
channelSchemas =
|
||||
|
@ -17,6 +18,10 @@ definitionSchemas =
|
|||
'misc': require './schemas/definitions/misc'
|
||||
|
||||
init = ->
|
||||
# Set up Backbone.Mediator schemas
|
||||
setUpDefinitions()
|
||||
setUpChannels()
|
||||
Backbone.Mediator.setValidationEnabled document.location.href.search(/codecombat.com/) is -1
|
||||
app.initialize()
|
||||
Backbone.history.start({ pushState: true })
|
||||
handleNormalUrls()
|
||||
|
@ -25,10 +30,6 @@ init = ->
|
|||
treemaExt.setup()
|
||||
filepicker.setKey('AvlkNoldcTOU4PvKi2Xm7z')
|
||||
|
||||
# Set up Backbone.Mediator schemas
|
||||
setUpDefinitions()
|
||||
setUpChannels()
|
||||
|
||||
$ -> init()
|
||||
|
||||
handleNormalUrls = ->
|
||||
|
|
|
@ -9,7 +9,7 @@ module.exports = class CocoClass
|
|||
@nicksUsed: {}
|
||||
@remainingNicks: []
|
||||
@nextNick: ->
|
||||
return "CocoClass " + classCount unless @nicks.length
|
||||
return (@name or "CocoClass") + " " + classCount unless @nicks.length
|
||||
@remainingNicks = if @remainingNicks.length then @remainingNicks else @nicks.slice()
|
||||
baseNick = @remainingNicks.splice(Math.floor(Math.random() * @remainingNicks.length), 1)[0]
|
||||
i = 0
|
||||
|
@ -37,7 +37,7 @@ module.exports = class CocoClass
|
|||
destroy: ->
|
||||
# teardown subscriptions, prevent new ones
|
||||
@stopListening?()
|
||||
@off()
|
||||
@off?()
|
||||
@unsubscribeAll()
|
||||
@stopListeningToShortcuts()
|
||||
@constructor.nicksUsed[@nick] = false
|
||||
|
@ -65,6 +65,7 @@ module.exports = class CocoClass
|
|||
Backbone.Mediator.subscribe(channel, func, @)
|
||||
|
||||
unsubscribeAll: ->
|
||||
return unless Backbone?.Mediator?
|
||||
for channel, func of @subscriptions
|
||||
func = utils.normalizeFunc(func, @)
|
||||
Backbone.Mediator.unsubscribe(channel, func, @)
|
||||
|
|
|
@ -47,7 +47,9 @@ module.exports = class God extends CocoClass
|
|||
|
||||
setLevel: (@level) ->
|
||||
setLevelSessionIDs: (@levelSessionIDs) ->
|
||||
setGoalManager: (goalManager) -> @angelsShare.goalManager = goalManager
|
||||
setGoalManager: (goalManager) ->
|
||||
@angelsShare.goalManager?.destroy() unless @angelsShare.goalManager is goalManager
|
||||
@angelsShare.goalManager = goalManager
|
||||
setWorldClassMap: (worldClassMap) -> @angelsShare.worldClassMap = worldClassMap
|
||||
|
||||
onTomeCast: (e) ->
|
||||
|
|
|
@ -63,12 +63,14 @@ module.exports = class LevelLoader extends CocoClass
|
|||
url += "?team=#{@team}" if @team
|
||||
|
||||
session = new LevelSession().setURL url
|
||||
@session = @supermodel.loadModel(session, 'level_session', {cache:false}).model
|
||||
@sessionResource = @supermodel.loadModel(session, 'level_session', {cache:false})
|
||||
@session = @sessionResource.model
|
||||
@session.once 'sync', -> @url = -> '/db/level.session/' + @id
|
||||
|
||||
if @opponentSessionID
|
||||
opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
|
||||
@opponentSession = @supermodel.loadModel(opponentSession, 'opponent_session').model
|
||||
@opponentSessionResource = @supermodel.loadModel(opponentSession, 'opponent_session')
|
||||
@opponentSession = @opponentSessionResource.model
|
||||
|
||||
# Supermodel (Level) Loading
|
||||
|
||||
|
@ -111,6 +113,8 @@ module.exports = class LevelLoader extends CocoClass
|
|||
@thangIDs = _.uniq thangIDs
|
||||
@thangNames = new ThangNamesCollection(@thangIDs)
|
||||
worldNecessities.push @supermodel.loadCollection(@thangNames, 'thang_names')
|
||||
worldNecessities.push @sessionResource if @sessionResource?.isLoading
|
||||
worldNecessities.push @opponentSessionResource if @opponentSessionResource?.isLoading
|
||||
|
||||
for obj in objUniq componentVersions
|
||||
url = "/db/level.component/#{obj.original}/version/#{obj.majorVersion}"
|
||||
|
@ -144,6 +148,7 @@ module.exports = class LevelLoader extends CocoClass
|
|||
|
||||
for thangTypeName in thangsToLoad
|
||||
thangType = nameModelMap[thangTypeName]
|
||||
continue if thangType.isFullyLoaded()
|
||||
thangType.fetch()
|
||||
thangType = @supermodel.loadModel(thangType, 'thang').model
|
||||
res = @supermodel.addSomethingResource "sprite_sheet", 5
|
||||
|
@ -165,16 +170,27 @@ module.exports = class LevelLoader extends CocoClass
|
|||
app.tracker.updatePlayState(@level, @session) unless @headless
|
||||
|
||||
buildLoop: =>
|
||||
return if @lastBuilt and new Date().getTime() - @lastBuilt < 10
|
||||
return clearInterval @buildLoopInterval unless @spriteSheetsToBuild.length
|
||||
|
||||
someLeft = false
|
||||
for spriteSheetResource, i in @spriteSheetsToBuild
|
||||
if spriteSheetResource.thangType.loaded
|
||||
@buildSpriteSheetsForThangType spriteSheetResource.thangType
|
||||
@spriteSheetsToBuild.splice i, 1
|
||||
@lastBuilt = new Date().getTime()
|
||||
spriteSheetResource.markLoaded()
|
||||
return
|
||||
continue if spriteSheetResource.spriteSheetKeys
|
||||
someLeft = true
|
||||
thangType = spriteSheetResource.thangType
|
||||
if thangType.loaded and not thangType.loading
|
||||
keys = @buildSpriteSheetsForThangType spriteSheetResource.thangType
|
||||
if keys and keys.length
|
||||
@listenTo spriteSheetResource.thangType, 'build-complete', @onBuildComplete
|
||||
spriteSheetResource.spriteSheetKeys = keys
|
||||
else
|
||||
spriteSheetResource.markLoaded()
|
||||
|
||||
clearInterval @buildLoopInterval unless someLeft
|
||||
|
||||
onBuildComplete: (e) ->
|
||||
resource = null
|
||||
for resource in @spriteSheetsToBuild
|
||||
break if e.thangType is resource.thangType
|
||||
resource.spriteSheetKeys = (k for k in resource.spriteSheetKeys when k isnt e.key)
|
||||
resource.markLoaded() if resource.spriteSheetKeys.length is 0
|
||||
|
||||
denormalizeSession: ->
|
||||
return if @headless or @sessionDenormalized or @spectateMode
|
||||
|
@ -201,13 +217,16 @@ module.exports = class LevelLoader extends CocoClass
|
|||
# queue = new createjs.LoadQueue()
|
||||
# queue.loadFile('/file/'+f)
|
||||
@grabThangTypeTeams() unless @thangTypeTeams
|
||||
keys = []
|
||||
for team in @thangTypeTeams[thangType.get('original')] ? [null]
|
||||
spriteOptions = {resolutionFactor: SPRITE_RESOLUTION_FACTOR, async: false}
|
||||
spriteOptions = {resolutionFactor: SPRITE_RESOLUTION_FACTOR, async: true}
|
||||
if thangType.get('kind') is 'Floor'
|
||||
spriteOptions.resolutionFactor = 2
|
||||
if team and color = @teamConfigs[team]?.color
|
||||
spriteOptions.colorConfig = team: color
|
||||
@buildSpriteSheet thangType, spriteOptions
|
||||
key = @buildSpriteSheet thangType, spriteOptions
|
||||
if _.isString(key) then keys.push key
|
||||
keys
|
||||
|
||||
grabThangTypeTeams: ->
|
||||
@grabTeamConfigs()
|
||||
|
|
|
@ -35,6 +35,7 @@ module.exports = class DOMScriptModule extends ScriptModule
|
|||
sides: dom.highlight.sides
|
||||
offset: dom.highlight.offset
|
||||
rotation: dom.highlight.rotation
|
||||
note.event = _.pick note.event, (value) -> not _.isUndefined value
|
||||
@maybeApplyDelayToNote note
|
||||
note
|
||||
|
||||
|
|
|
@ -52,12 +52,14 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
@debugScripts = @view.getQueryVariable 'dev'
|
||||
@initProperties()
|
||||
@addScriptSubscriptions()
|
||||
@beginTicking()
|
||||
|
||||
setScripts: (@originalScripts) ->
|
||||
@quiet = true
|
||||
@initProperties()
|
||||
@loadFromSession()
|
||||
@quiet = false
|
||||
@addScriptSubscriptions()
|
||||
@run()
|
||||
|
||||
initProperties: ->
|
||||
|
@ -74,6 +76,25 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
script.id = (idNum++).toString() unless script.id
|
||||
callback = makeCallback(script.channel) # curry in the channel argument
|
||||
@addNewSubscription(script.channel, callback)
|
||||
|
||||
beginTicking: ->
|
||||
@tickInterval = setInterval @tick, 5000
|
||||
|
||||
tick: =>
|
||||
scriptStates = {}
|
||||
now = new Date()
|
||||
for script in @scripts
|
||||
scriptStates[script.id] =
|
||||
timeSinceLastEnded: (if script.lastEnded then now - script.lastEnded else 0) / 1000
|
||||
timeSinceLastTriggered: (if script.lastTriggered then now - script.lastTriggered else 0) / 1000
|
||||
|
||||
stateEvent =
|
||||
scriptRunning: @currentNoteGroup?.scriptID or ''
|
||||
noteGroupRunning: @currentNoteGroup?.name or ''
|
||||
scriptStates: scriptStates
|
||||
timeSinceLastScriptEnded: (if @lastScriptEnded then now - @lastScriptEnded else 0) / 1000
|
||||
|
||||
Backbone.Mediator.publish 'script-manager:tick', stateEvent
|
||||
|
||||
loadFromSession: ->
|
||||
# load the queue with note groups to skip through
|
||||
|
@ -88,6 +109,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
return unless script
|
||||
@triggered.push(script.id)
|
||||
noteChain = @processScript(script)
|
||||
return unless noteChain
|
||||
if scripts.currentScriptOffset
|
||||
noteGroup.skipMe = true for noteGroup in noteChain[..scripts.currentScriptOffset-1]
|
||||
@addNoteChain(noteChain, false)
|
||||
|
@ -107,6 +129,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
@triggered.push(scriptID)
|
||||
@ended.push(scriptID)
|
||||
noteChain = @processScript(script)
|
||||
return unless noteChain
|
||||
noteGroup.skipMe = true for noteGroup in noteChain
|
||||
@addNoteChain(noteChain, false)
|
||||
|
||||
|
@ -123,6 +146,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
|
||||
destroy: ->
|
||||
@onEndAll()
|
||||
clearInterval @tickInterval
|
||||
super()
|
||||
|
||||
# TRIGGERERING NOTES
|
||||
|
@ -147,10 +171,11 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
continue unless @scriptPrereqsSatisfied(script)
|
||||
continue unless scriptMatchesEventPrereqs(script, event)
|
||||
# everything passed!
|
||||
console.log "SCRIPT: Running script '#{script.id}'" if @debugScripts
|
||||
console.debug "SCRIPT: Running script '#{script.id}'" if @debugScripts
|
||||
script.lastTriggered = new Date().getTime()
|
||||
@triggered.push(script.id) unless alreadyTriggered
|
||||
noteChain = @processScript(script)
|
||||
if not noteChain then return @trackScriptCompletions (script.id)
|
||||
@addNoteChain(noteChain)
|
||||
@run()
|
||||
|
||||
|
@ -159,10 +184,10 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
|
||||
processScript: (script) ->
|
||||
noteChain = script.noteChain
|
||||
return null unless noteChain?.length
|
||||
noteGroup.scriptID = script.id for noteGroup in noteChain
|
||||
if noteChain.length
|
||||
lastNoteGroup = noteChain[noteChain.length - 1]
|
||||
lastNoteGroup.isLast = true
|
||||
lastNoteGroup = noteChain[noteChain.length - 1]
|
||||
lastNoteGroup.isLast = true
|
||||
return noteChain
|
||||
|
||||
addNoteChain: (noteChain, clearYields=true) ->
|
||||
|
@ -207,7 +232,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
@notifyScriptStateChanged()
|
||||
@scriptInProgress = true
|
||||
@currentTimeouts = []
|
||||
console.log "SCRIPT: Starting note group '#{nextNoteGroup.name}'" if @debugScripts
|
||||
console.debug "SCRIPT: Starting note group '#{nextNoteGroup.name}'" if @debugScripts
|
||||
for module in nextNoteGroup.modules
|
||||
@processNote(note, nextNoteGroup) for note in module.startNotes()
|
||||
if nextNoteGroup.script.duration
|
||||
|
@ -221,12 +246,12 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
@ignoreEvents = true
|
||||
for noteGroup, i in @noteGroupQueue
|
||||
break unless noteGroup.skipMe
|
||||
console.log "SCRIPT: Skipping note group '#{noteGroup.name}'" if @debugScripts
|
||||
console.debug "SCRIPT: Skipping note group '#{noteGroup.name}'" if @debugScripts
|
||||
@processNoteGroup(noteGroup)
|
||||
for module in noteGroup.modules
|
||||
notes = module.skipNotes()
|
||||
@processNote(note, noteGroup) for note in notes
|
||||
@trackScriptCompletions(noteGroup)
|
||||
@trackScriptCompletionsFromNoteGroup(noteGroup)
|
||||
@noteGroupQueue = @noteGroupQueue[i..]
|
||||
@ignoreEvents = false
|
||||
|
||||
|
@ -268,14 +293,13 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
return if @ending # kill infinite loops right here
|
||||
@ending = true
|
||||
return unless @currentNoteGroup?
|
||||
console.log "SCRIPT: Ending note group '#{@currentNoteGroup.name}'" if @debugScripts
|
||||
console.debug "SCRIPT: Ending note group '#{@currentNoteGroup.name}'" if @debugScripts
|
||||
clearTimeout(timeout) for timeout in @currentTimeouts
|
||||
for module in @currentNoteGroup.modules
|
||||
@processNote(note, @currentNoteGroup) for note in module.endNotes()
|
||||
Backbone.Mediator.publish 'note-group-ended' unless @quiet
|
||||
@scriptInProgress = false
|
||||
@ended.push(@currentNoteGroup.scriptID) if @currentNoteGroup.isLast
|
||||
@trackScriptCompletions(@currentNoteGroup)
|
||||
@trackScriptCompletionsFromNoteGroup(@currentNoteGroup)
|
||||
@currentNoteGroup = null
|
||||
unless @noteGroupQueue.length
|
||||
@notifyScriptStateChanged()
|
||||
|
@ -302,7 +326,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
for module in noteGroup.modules
|
||||
notes = module.skipNotes()
|
||||
@processNote(note, noteGroup) for note in notes unless @quiet
|
||||
@trackScriptCompletions(noteGroup) unless @quiet
|
||||
@trackScriptCompletionsFromNoteGroup(noteGroup) unless @quiet
|
||||
|
||||
@noteGroupQueue = []
|
||||
|
||||
|
@ -317,11 +341,18 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
|
|||
Backbone.Mediator.publish 'level-enable-controls', {}
|
||||
Backbone.Mediator.publish 'level-set-letterbox', { on: false }
|
||||
|
||||
trackScriptCompletions: (noteGroup) ->
|
||||
return if @quiet
|
||||
trackScriptCompletionsFromNoteGroup: (noteGroup) ->
|
||||
return unless noteGroup.isLast
|
||||
@ended.push(noteGroup.scriptID) unless noteGroup.scriptID in @ended
|
||||
Backbone.Mediator.publish 'script:ended', {scriptID: noteGroup.scriptID}
|
||||
@trackScriptCompletions(noteGroup.scriptID)
|
||||
|
||||
trackScriptCompletions: (scriptID) ->
|
||||
return if @quiet
|
||||
@ended.push(scriptID) unless scriptID in @ended
|
||||
for script in @scripts
|
||||
if script.id is scriptID
|
||||
script.lastEnded = new Date()
|
||||
@lastScriptEnded = new Date()
|
||||
Backbone.Mediator.publish 'script:ended', {scriptID: scriptID}
|
||||
|
||||
notifyScriptStateChanged: ->
|
||||
return if @quiet
|
||||
|
|
|
@ -36,4 +36,4 @@ module.exports = class ScriptModule extends CocoClass
|
|||
Math.max(0, sums...)
|
||||
|
||||
maybeApplyDelayToNote: (note) ->
|
||||
note.delay = @scrubbingTime + @movementTime
|
||||
note.delay = (@scrubbingTime + @movementTime) or 0
|
|
@ -23,7 +23,7 @@ module.exports = class Simulator extends CocoClass
|
|||
@cleanupSimulation()
|
||||
@god?.destroy()
|
||||
super()
|
||||
|
||||
|
||||
fetchAndSimulateOneGame: (humanGameID, ogresGameID) =>
|
||||
return if @destroyed
|
||||
$.ajax
|
||||
|
@ -34,20 +34,22 @@ module.exports = class Simulator extends CocoClass
|
|||
"humansGameID": humanGameID
|
||||
"ogresGameID": ogresGameID
|
||||
error: (errorData) ->
|
||||
console.log "There was an error fetching two games! #{JSON.stringify errorData}"
|
||||
console.warn "There was an error fetching two games! #{JSON.stringify errorData}"
|
||||
success: (taskData) =>
|
||||
return if @destroyed
|
||||
@trigger 'statusUpdate', 'Setting up simulation...'
|
||||
#refactor this
|
||||
@task = new SimulationTask(taskData)
|
||||
|
||||
|
||||
@supermodel ?= new SuperModel()
|
||||
@supermodel.resetProgress()
|
||||
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @task.getLevelName(), sessionID: @task.getFirstSessionID(), headless: true
|
||||
|
||||
|
||||
if @supermodel.finished()
|
||||
@simulateSingleGame()
|
||||
else
|
||||
@listenToOnce @supermodel, 'loaded-all', @simulateSingleGame
|
||||
|
||||
simulateSingleGame: ->
|
||||
return if @destroyed
|
||||
@trigger 'statusUpdate', 'Simulating...'
|
||||
|
@ -55,32 +57,31 @@ module.exports = class Simulator extends CocoClass
|
|||
@setupGod()
|
||||
try
|
||||
@commenceSingleSimulation()
|
||||
catch err
|
||||
console.log err
|
||||
@handleSingleSimulationError()
|
||||
|
||||
catch error
|
||||
@handleSingleSimulationError error
|
||||
|
||||
commenceSingleSimulation: ->
|
||||
@god.createWorld @generateSpellsObject()
|
||||
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @handleSingleSimulationInfiniteLoop, @
|
||||
Backbone.Mediator.subscribeOnce 'god:goals-calculated', @processSingleGameResults, @
|
||||
|
||||
handleSingleSimulationError: ->
|
||||
console.log "There was an error simulating a single game!"
|
||||
|
||||
handleSingleSimulationError: (error) ->
|
||||
console.error "There was an error simulating a single game!", error
|
||||
if @options.headlessClient
|
||||
console.log "GAMERESULT:tie"
|
||||
process.exit(0)
|
||||
@cleanupSimulation()
|
||||
|
||||
|
||||
handleSingleSimulationInfiniteLoop: ->
|
||||
console.log "There was an infinite loop in the single game!"
|
||||
if @options.headlessClient
|
||||
console.log "GAMERESULT:tie"
|
||||
process.exit(0)
|
||||
@cleanupSimulation()
|
||||
|
||||
|
||||
processSingleGameResults: (simulationResults) ->
|
||||
console.log "Processing results!"
|
||||
taskResults = @formTaskResultsObject simulationResults
|
||||
console.log "Processing results:", taskResults
|
||||
humanSessionRank = taskResults.sessions[0].metrics.rank
|
||||
ogreSessionRank = taskResults.sessions[1].metrics.rank
|
||||
if @options.headlessClient
|
||||
|
@ -93,12 +94,12 @@ module.exports = class Simulator extends CocoClass
|
|||
process.exit(0)
|
||||
else
|
||||
@sendSingleGameBackToServer(taskResults)
|
||||
|
||||
|
||||
@cleanupSimulation()
|
||||
|
||||
|
||||
sendSingleGameBackToServer: (results) ->
|
||||
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
|
||||
|
||||
|
||||
$.ajax
|
||||
url: "/queue/scoring/recordTwoGames"
|
||||
data: results
|
||||
|
@ -107,8 +108,8 @@ module.exports = class Simulator extends CocoClass
|
|||
success: @handleTaskResultsTransferSuccess
|
||||
error: @handleTaskResultsTransferError
|
||||
complete: @cleanupAndSimulateAnotherTask
|
||||
|
||||
|
||||
|
||||
|
||||
fetchAndSimulateTask: =>
|
||||
return if @destroyed
|
||||
|
||||
|
@ -134,7 +135,7 @@ module.exports = class Simulator extends CocoClass
|
|||
console.error "There was a horrible Error: #{JSON.stringify errorData}"
|
||||
@trigger 'statusUpdate', 'There was an error fetching games to simulate. Retrying in 10 seconds.'
|
||||
@simulateAnotherTaskAfterDelay()
|
||||
|
||||
|
||||
|
||||
handleNoGamesResponse: ->
|
||||
info = 'Finding game to simulate...'
|
||||
|
@ -191,7 +192,7 @@ module.exports = class Simulator extends CocoClass
|
|||
|
||||
setupGod: ->
|
||||
@god.setLevel @level.serialize @supermodel
|
||||
@god.setLevelSessionIDs (session.id for session in @task.getSessions())
|
||||
@god.setLevelSessionIDs (session.sessionID for session in @task.getSessions())
|
||||
@god.setWorldClassMap @world.classMap
|
||||
@god.setGoalManager new GoalManager(@world, @level.get 'goals')
|
||||
|
||||
|
@ -235,7 +236,7 @@ module.exports = class Simulator extends CocoClass
|
|||
|
||||
sendResultsBackToServer: (results) ->
|
||||
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
|
||||
console.log "Sending result back to server!", results
|
||||
console.log "Sending result back to server:", results
|
||||
|
||||
if @options.headlessClient and @options.testing
|
||||
return @fetchAndSimulateTask()
|
||||
|
@ -250,9 +251,10 @@ module.exports = class Simulator extends CocoClass
|
|||
complete: @cleanupAndSimulateAnotherTask
|
||||
|
||||
handleTaskResultsTransferSuccess: (result) =>
|
||||
return if @destroyed
|
||||
console.log "Task registration result: #{JSON.stringify result}"
|
||||
@trigger 'statusUpdate', 'Results were successfully sent back to server!'
|
||||
console.log "Simulated by you: " + @simulatedByYou
|
||||
console.log "Simulated by you:", @simulatedByYou
|
||||
@simulatedByYou++
|
||||
unless @options.headlessClient
|
||||
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
|
||||
|
@ -260,10 +262,12 @@ module.exports = class Simulator extends CocoClass
|
|||
application.tracker?.trackEvent 'Simulator Result', label: "Success"
|
||||
|
||||
handleTaskResultsTransferError: (error) =>
|
||||
return if @destroyed
|
||||
@trigger 'statusUpdate', 'There was an error sending the results back to the server.'
|
||||
console.log "Task registration error: #{JSON.stringify error}"
|
||||
|
||||
cleanupAndSimulateAnotherTask: =>
|
||||
return if @destroyed
|
||||
@cleanupSimulation()
|
||||
@fetchAndSimulateTask()
|
||||
|
||||
|
@ -472,6 +476,5 @@ class SimulationTask
|
|||
spellKey = pathComponents.join '/'
|
||||
@thangSpells[thang.id].push spellKey
|
||||
if not method.cloneOf and spellKey is desiredSpellKey
|
||||
console.log "Setting #{desiredSpellKey} from world!"
|
||||
|
||||
#console.log "Setting #{desiredSpellKey} from world!"
|
||||
return method.source
|
||||
|
|
|
@ -33,6 +33,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
camera: null
|
||||
spriteSheetCache: null
|
||||
showInvisible: false
|
||||
async: true
|
||||
|
||||
possessed: false
|
||||
flipped: false
|
||||
|
@ -75,28 +76,32 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
@ranges = []
|
||||
@handledDisplayEvents = {}
|
||||
@age = 0
|
||||
@stillLoading = true
|
||||
if @thangType.isFullyLoaded()
|
||||
@setupSprite()
|
||||
else
|
||||
@stillLoading = true
|
||||
@thangType.fetch()
|
||||
@listenToOnce(@thangType, 'sync', @setupSprite)
|
||||
|
||||
setupSprite: ->
|
||||
for trigger, sounds of @thangType.get('soundTriggers') or {} when trigger isnt 'say'
|
||||
AudioPlayer.preloadSoundReference sound for sound in sounds
|
||||
@stillLoading = false
|
||||
if @thangType.get('raster')
|
||||
@stillLoading = false
|
||||
@actions = {}
|
||||
@isRaster = true
|
||||
@setUpRasterImage()
|
||||
else
|
||||
@actions = @thangType.getActions()
|
||||
@buildFromSpriteSheet @buildSpriteSheet()
|
||||
@createMarks()
|
||||
result = @buildSpriteSheet()
|
||||
if _.isString result # async build
|
||||
@listenToOnce @thangType, 'build-complete', @setupSprite
|
||||
else
|
||||
@stillLoading = false
|
||||
@actions = @thangType.getActions()
|
||||
@buildFromSpriteSheet result
|
||||
@createMarks()
|
||||
|
||||
finishSetup: ->
|
||||
return unless @thang
|
||||
@updateBaseScale()
|
||||
@scaleFactor = @thang.scaleFactor if @thang?.scaleFactor
|
||||
@update true # Reflect initial scale and other state
|
||||
|
@ -120,7 +125,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
buildSpriteSheet: ->
|
||||
options = _.extend @options, @thang?.getSpriteOptions?() ? {}
|
||||
options.colorConfig = @options.colorConfig if @options.colorConfig
|
||||
options.async = false
|
||||
options.async = @options.async
|
||||
@thangType.getSpriteSheet options
|
||||
|
||||
setImageObject: (newImageObject) ->
|
||||
|
@ -677,6 +682,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
updateGold: ->
|
||||
# TODO: eventually this should be moved into some sort of team-based update
|
||||
# rather than an each-thang-that-shows-gold-per-team thing.
|
||||
return unless @thang
|
||||
return if @thang.gold is @lastGold
|
||||
gold = Math.floor @thang.gold
|
||||
if @thang.world.age is 0
|
||||
|
@ -752,7 +758,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
|
|||
|
||||
endFunc = =>
|
||||
@lastTween = null
|
||||
@imageObject.gotoAndPlay(endAnimation)
|
||||
@imageObject.gotoAndPlay(endAnimation) unless @stillLoading
|
||||
@shadow.action = 'idle'
|
||||
@update true
|
||||
@possessed = false
|
||||
|
|
|
@ -181,7 +181,8 @@ module.exports = class Mark extends CocoClass
|
|||
|
||||
return @listenToOnce(@thangType, 'sync', @onLoadedThangType) if not @thangType.loaded
|
||||
CocoSprite = require './CocoSprite'
|
||||
markSprite = new CocoSprite @thangType, @thangType.spriteOptions
|
||||
# don't bother with making these render async for now, but maybe later for fun and more complexity of code
|
||||
markSprite = new CocoSprite @thangType, {async: false}
|
||||
markSprite.queueAction 'idle'
|
||||
@mark = markSprite.imageObject
|
||||
@markSprite = markSprite
|
||||
|
@ -234,28 +235,27 @@ module.exports = class Mark extends CocoClass
|
|||
if @name is 'debug' or (@name is 'shadow' and @sprite.thang?.shape in ["rectangle", "box"])
|
||||
@mark.rotation = @sprite.thang.rotation * 180 / Math.PI
|
||||
|
||||
updateScale: ->
|
||||
updateScale: (log) ->
|
||||
if @name is 'bounds' and (@sprite.thang.width isnt @lastWidth or @sprite.thang.height isnt @lastHeight)
|
||||
oldMark = @mark
|
||||
@buildBounds()
|
||||
oldMark.parent.addChild @mark
|
||||
oldMark.parent.swapChildren oldMark, @mark
|
||||
oldMark.parent.removeChild oldMark
|
||||
|
||||
if @markSprite?
|
||||
@markSprite.scaleFactor = 1.2
|
||||
@markSprite.updateScale()
|
||||
return unless @name in ["selection", "target", "repair", "highlight"]
|
||||
scale = 0.5
|
||||
if @sprite?.imageObject
|
||||
size = @sprite.getAverageDimension()
|
||||
size += 60 if @name is 'selection'
|
||||
size += 60 if @name is 'repair'
|
||||
size *= @sprite.scaleFactor
|
||||
scale = size / {selection: 128, target: 128, repair: 320, highlight: 160}[@name]
|
||||
scale /= 3
|
||||
if @sprite?.thang.spriteName.search(/(dungeon|indoor).wall/i) isnt -1
|
||||
scale *= 2
|
||||
|
||||
if @markSprite?
|
||||
@markSprite.scaleFactor = scale
|
||||
@markSprite.updateScale()
|
||||
else
|
||||
@mark.scaleX = @mark.scaleY = Math.min 1, scale
|
||||
if @name in ['selection', 'target', 'repair']
|
||||
@mark.scaleY *= @camera.y2x # code applies perspective
|
||||
|
|
|
@ -21,21 +21,33 @@ module.exports = class MusicPlayer extends CocoClass
|
|||
|
||||
onPlayMusic: (e) ->
|
||||
src = e.file
|
||||
if src
|
||||
src = "/file#{src}#{AudioPlayer.ext}"
|
||||
return @currentMusic.play('none', 0, 0, -1, 0.3) if src is @currentMusic?.src
|
||||
media = AudioPlayer.getStatus(src)
|
||||
if not media?.loaded
|
||||
AudioPlayer.preloadSound(src)
|
||||
@standingBy = e
|
||||
return
|
||||
src = "/file#{e.file}#{AudioPlayer.ext}"
|
||||
if (not e.file) or src is @currentMusic?.src
|
||||
if e.play then @restartCurrentMusic() else @fadeOutCurrentMusic()
|
||||
return
|
||||
|
||||
media = AudioPlayer.getStatus(src)
|
||||
if not media?.loaded
|
||||
AudioPlayer.preloadSound(src)
|
||||
@standingBy = e
|
||||
return
|
||||
|
||||
@standingBy = null
|
||||
if @currentMusic
|
||||
f = -> @stop()
|
||||
createjs.Tween.get(@currentMusic).to({volume:0.0}, CROSSFADE_LENGTH).call(f)
|
||||
|
||||
@currentMusic = createjs.Sound.play(src, 'none', 0, 0, -1, 0.3) if src and e.play
|
||||
@fadeOutCurrentMusic()
|
||||
@startNewMusic(src) if e.play
|
||||
|
||||
restartCurrentMusic: ->
|
||||
return unless @currentMusic
|
||||
@currentMusic.play('none', 0, 0, -1, 0.3)
|
||||
@updateMusicVolume()
|
||||
|
||||
fadeOutCurrentMusic: ->
|
||||
return unless @currentMusic
|
||||
f = -> @stop()
|
||||
createjs.Tween.get(@currentMusic).to({volume:0.0}, CROSSFADE_LENGTH).call(f)
|
||||
|
||||
startNewMusic: (src) ->
|
||||
@currentMusic = createjs.Sound.play(src, 'none', 0, 0, -1, 0.3) if src
|
||||
return unless @currentMusic
|
||||
@currentMusic.volume = 0.0
|
||||
if me.get('music')
|
||||
|
|
|
@ -103,6 +103,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@stage.removeAllChildren()
|
||||
@stage.removeEventListener 'stagemousemove', @onMouseMove
|
||||
@stage.removeEventListener 'stagemousedown', @onMouseDown
|
||||
@stage.removeEventListener 'stagemouseup', @onMouseUp
|
||||
@stage.removeAllEventListeners()
|
||||
@stage.enableDOMEvents false
|
||||
@stage.enableMouseOver 0
|
||||
|
@ -281,6 +282,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
onSetPlaying: (e) ->
|
||||
@playing = (e ? {}).playing ? true
|
||||
@setPlayingCalled = true
|
||||
if @playing and @currentFrame >= (@world.totalFrames - 5)
|
||||
@currentFrame = 0
|
||||
if @fastForwarding and not @playing
|
||||
|
@ -352,6 +354,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@casting = true
|
||||
@wasPlayingWhenCastingBegan = @playing
|
||||
Backbone.Mediator.publish 'level-set-playing', { playing: false }
|
||||
@setPlayingCalled = false # don't overwrite playing settings if they changed by, say, scripts
|
||||
|
||||
if @coordinateDisplay?
|
||||
@surfaceTextLayer.removeChild @coordinateDisplay
|
||||
|
@ -370,7 +373,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
|
||||
# This has a tendency to break scripts that are waiting for playback to change when the level is loaded
|
||||
# so only run it after the first world is created.
|
||||
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan } unless event.firstWorld
|
||||
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan } unless event.firstWorld or @setPlayingCalled
|
||||
|
||||
fastForwardTo = null
|
||||
if @playing
|
||||
|
@ -414,6 +417,7 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
@stage.enableMouseOver(10)
|
||||
@stage.addEventListener 'stagemousemove', @onMouseMove
|
||||
@stage.addEventListener 'stagemousedown', @onMouseDown
|
||||
@stage.addEventListener 'stagemouseup', @onMouseUp
|
||||
@canvas.on 'mousewheel', @onMouseWheel
|
||||
@hookUpChooseControls() if @options.choosing
|
||||
createjs.Ticker.timingMode = createjs.Ticker.RAF_SYNCHED
|
||||
|
@ -537,6 +541,11 @@ module.exports = Surface = class Surface extends CocoClass
|
|||
onBackground = not @stage.hitTest e.stageX, e.stageY
|
||||
Backbone.Mediator.publish 'surface:stage-mouse-down', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
|
||||
|
||||
onMouseUp: (e) =>
|
||||
return if @disabled
|
||||
onBackground = not @stage.hitTest e.stageX, e.stageY
|
||||
Backbone.Mediator.publish 'surface:stage-mouse-up', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
|
||||
|
||||
onMouseWheel: (e) =>
|
||||
# https://github.com/brandonaaron/jquery-mousewheel
|
||||
e.preventDefault()
|
||||
|
|
|
@ -39,8 +39,6 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
else if options.name
|
||||
@setNameLabel options.name
|
||||
|
||||
finishSetup: -> # No initial setup update needed.
|
||||
|
||||
makeIndieThang: (thangType, thangID, pos) ->
|
||||
thang = super thangType, thangID, pos
|
||||
thang.isSelectable = false
|
||||
|
@ -49,6 +47,13 @@ module.exports = class WizardSprite extends IndieSprite
|
|||
thang.pos.z += thang.bobHeight
|
||||
thang
|
||||
|
||||
finishSetup: ->
|
||||
@updateBaseScale()
|
||||
@scaleFactor = @thang.scaleFactor if @thang?.scaleFactor
|
||||
@updateScale()
|
||||
@updateRotation()
|
||||
# Don't call general update() because Thang isn't built yet
|
||||
|
||||
onPlayerStatesChanged: (e) ->
|
||||
for playerID, state of e.states
|
||||
continue unless playerID is @thang.id
|
||||
|
|
|
@ -14,6 +14,7 @@ module.exports = class GoalManager extends CocoClass
|
|||
# If you want weird goals or hybrid goals, make a custom goal.
|
||||
|
||||
nextGoalID: 0
|
||||
nicks: ["GoalManager"]
|
||||
|
||||
constructor: (@world, @initialGoals, @team) ->
|
||||
super()
|
||||
|
|
|
@ -185,6 +185,22 @@ module.exports.thangNames = thangNames =
|
|||
"Lacos"
|
||||
"Upfish"
|
||||
]
|
||||
"Ogre Peon M": [
|
||||
"Durbo"
|
||||
"Kurger"
|
||||
"Mudwich"
|
||||
"Ba Bo"
|
||||
"Zugger"
|
||||
"Toe Pod"
|
||||
]
|
||||
"Ogre Peon F": [
|
||||
"Iblet"
|
||||
"Lorba"
|
||||
"Zzoya"
|
||||
"Yamra"
|
||||
"Greeke"
|
||||
"Vapa"
|
||||
]
|
||||
"Ogre M": [
|
||||
"Krogg"
|
||||
"Dronck"
|
||||
|
|
|
@ -3,10 +3,10 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
loading: "Carregando..."
|
||||
saving: "Salvando..."
|
||||
sending: "Enviando..."
|
||||
# send: "Send"
|
||||
send: "Enviar"
|
||||
cancel: "Cancelar"
|
||||
save: "Salvar"
|
||||
# publish: "Publish"
|
||||
publish: "Publicar"
|
||||
create: "Criar"
|
||||
delay_1_sec: "1 segundo"
|
||||
delay_3_sec: "3 segundos"
|
||||
|
@ -14,7 +14,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
manual: "Manual"
|
||||
fork: "Fork"
|
||||
play: "Jogar"
|
||||
# retry: "Retry"
|
||||
retry: "Tente novamente"
|
||||
# watch: "Watch"
|
||||
# unwatch: "Unwatch"
|
||||
# submit_patch: "Submit Patch"
|
||||
|
@ -36,11 +36,11 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
|
||||
nav:
|
||||
play: "Jogar"
|
||||
# community: "Community"
|
||||
community: "Comunidade"
|
||||
editor: "Editor"
|
||||
blog: "Blog"
|
||||
forum: "Fórum"
|
||||
# account: "Account"
|
||||
account: "Conta"
|
||||
admin: "Administrador"
|
||||
home: "Início"
|
||||
contribute: "Contribuir"
|
||||
|
@ -78,7 +78,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
creating: "Criando a nova conta..."
|
||||
sign_up: "Criar conta"
|
||||
log_in: "Entre com a senha"
|
||||
# social_signup: "Or, you can sign up through Facebook or G+:"
|
||||
social_signup: "Ou, você pode fazer login pelo Facebook ou G+:"
|
||||
|
||||
home:
|
||||
slogan: "Aprenda a programar em JavaScript enquanto se diverte com um jogo."
|
||||
|
@ -133,15 +133,15 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
wizard_settings:
|
||||
title: "Configurações do Feiticeiro"
|
||||
customize_avatar: "Personalize o seu Avatar"
|
||||
# active: "Active"
|
||||
# color: "Color"
|
||||
# group: "Group"
|
||||
active: "Ativo"
|
||||
color: "Cor"
|
||||
group: "Grupo"
|
||||
clothes: "Roupas"
|
||||
trim: "Aparar"
|
||||
cloud: "Nuvem"
|
||||
# team: "Team"
|
||||
team: "Time"
|
||||
spell: "Feitiço"
|
||||
boots: "Boots"
|
||||
boots: "Botas"
|
||||
hue: "Matiz"
|
||||
saturation: "Saturação"
|
||||
lightness: "Luminosidade"
|
||||
|
@ -152,7 +152,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
autosave: "As alterações serão salvas automaticamente."
|
||||
me_tab: "Eu"
|
||||
picture_tab: "Foto"
|
||||
# upload_picture: "Upload a picture"
|
||||
upload_picture: "Enviar uma foto"
|
||||
wizard_tab: "Feiticeiro"
|
||||
password_tab: "Senha"
|
||||
emails_tab: "Emails"
|
||||
|
@ -167,8 +167,8 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
# email_notifications_summary: "Controls for personalized, automatic email notifications related to your CodeCombat activity."
|
||||
# email_any_notes: "Any Notifications"
|
||||
# email_any_notes_description: "Disable to stop all activity notification emails."
|
||||
# email_recruit_notes: "Job Opportunities"
|
||||
# email_recruit_notes_description: "If you play really well, we may contact you about getting you a (better) job."
|
||||
email_recruit_notes: "Oportunidades de emprego"
|
||||
email_recruit_notes_description: "Se você jogar muito bem, nós podemos lhe contactar para lhe oferecer um emprego (melhor)"
|
||||
contributor_emails: "Emails para as Classes de Contribuidores"
|
||||
contribute_prefix: "Estamos procurando pessoas para se juntar à nossa turma! Confira a nossa "
|
||||
contribute_page: "página de contribuição"
|
||||
|
@ -180,22 +180,22 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
# job_profile: "Job Profile"
|
||||
# job_profile_approved: "Your job profile has been approved by CodeCombat. Employers will be able to see it until you either mark it inactive or it has not been changed for four weeks."
|
||||
# job_profile_explanation: "Hi! Fill this out, and we will get in touch about finding you a software developer job."
|
||||
# sample_profile: "See a sample profile"
|
||||
# view_profile: "View Your Profile"
|
||||
sample_profile: "Veja um perfil de exemplo"
|
||||
view_profile: "Visualizar seu perfil"
|
||||
|
||||
account_profile:
|
||||
edit_settings: "Editar as configurações"
|
||||
profile_for_prefix: "Perfil de "
|
||||
profile_for_suffix: ""
|
||||
# approved: "Approved"
|
||||
# not_approved: "Not Approved"
|
||||
approved: "Aprovado"
|
||||
not_approved: "Não Aprovado"
|
||||
# looking_for: "Looking for:"
|
||||
# last_updated: "Last updated:"
|
||||
# contact: "Contact"
|
||||
last_updated: "Última atualização:"
|
||||
contact: "Contato"
|
||||
# work_experience: "Work Experience"
|
||||
# education: "Education"
|
||||
education: "Formação"
|
||||
# our_notes: "Our Notes"
|
||||
# projects: "Projects"
|
||||
projects: "Projetos"
|
||||
|
||||
# employers:
|
||||
# want_to_hire_our_players: "Want to hire expert CodeCombat players?"
|
||||
|
@ -203,8 +203,8 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
# candidates_count_prefix: "We currently have "
|
||||
# candidates_count_many: "many"
|
||||
# candidates_count_suffix: "highly skilled and vetted developers looking for work."
|
||||
# candidate_name: "Name"
|
||||
# candidate_location: "Location"
|
||||
candidate_name: "Nome"
|
||||
candidate_location: "Localização"
|
||||
# candidate_looking_for: "Looking For"
|
||||
# candidate_role: "Role"
|
||||
# candidate_top_skills: "Top Skills"
|
||||
|
@ -421,7 +421,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
results: "Resultados"
|
||||
description: "Descrição"
|
||||
or: "ou"
|
||||
# subject: "Subject"
|
||||
subject: "Assunto"
|
||||
email: "Email"
|
||||
password: "Senha"
|
||||
message: "Mensagem"
|
||||
|
@ -437,7 +437,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
easy: "Fácil"
|
||||
medium: "Médio"
|
||||
hard: "Difícil"
|
||||
# player: "Player"
|
||||
player: "Jogador"
|
||||
|
||||
about:
|
||||
who_is_codecombat: "Quem é CodeCombat?"
|
||||
|
@ -705,8 +705,8 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
# unknown: "Unknown error."
|
||||
|
||||
# resources:
|
||||
# your_sessions: "Your Sessions"
|
||||
# level: "Level"
|
||||
your_sessions: "Suas sessões"
|
||||
level: "Nível"
|
||||
# social_network_apis: "Social Network APIs"
|
||||
# facebook_status: "Facebook Status"
|
||||
# facebook_friends: "Facebook Friends"
|
||||
|
@ -719,27 +719,27 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
|
|||
# patches: "Patches"
|
||||
# patched_model: "Source Document"
|
||||
# model: "Model"
|
||||
# system: "System"
|
||||
# component: "Component"
|
||||
# components: "Components"
|
||||
system: "Sistema"
|
||||
component: "Componente"
|
||||
components: "Componentes"
|
||||
# thang: "Thang"
|
||||
# thangs: "Thangs"
|
||||
# level_session: "Your Session"
|
||||
level_session: "Sua sessão"
|
||||
# opponent_session: "Opponent Session"
|
||||
# article: "Article"
|
||||
# user_names: "User Names"
|
||||
article: "Artigos"
|
||||
user_names: "Nomes de usuário"
|
||||
# thang_names: "Thang Names"
|
||||
# files: "Files"
|
||||
files: "Arquivos"
|
||||
# top_simulators: "Top Simulators"
|
||||
# source_document: "Source Document"
|
||||
# document: "Document"
|
||||
# sprite_sheet: "Sprite Sheet"
|
||||
document: "Documento"
|
||||
sprite_sheet: "Planilha"
|
||||
|
||||
# delta:
|
||||
# added: "Added"
|
||||
# modified: "Modified"
|
||||
# deleted: "Deleted"
|
||||
added: "Adicionado"
|
||||
modified: "Modificado"
|
||||
deleted: "Removido"
|
||||
# moved_index: "Moved Index"
|
||||
# text_diff: "Text Diff"
|
||||
# merge_conflict_with: "MERGE CONFLICT WITH"
|
||||
# no_changes: "No Changes"
|
||||
merge_conflict_with: "CONFLITO DE MERGE COM"
|
||||
no_changes: "Sem mudanças"
|
||||
|
|
|
@ -37,7 +37,7 @@ class CocoModel extends Backbone.Model
|
|||
@loading = false
|
||||
@markToRevert()
|
||||
@loadFromBackup()
|
||||
|
||||
|
||||
getNormalizedURL: -> "#{@urlRoot}/#{@id}"
|
||||
|
||||
set: ->
|
||||
|
@ -69,11 +69,17 @@ class CocoModel extends Backbone.Model
|
|||
@set 'editPath', document.location.pathname
|
||||
options ?= {}
|
||||
success = options.success
|
||||
options.success = (resp) =>
|
||||
error = options.error
|
||||
options.success = (model, res) =>
|
||||
@trigger "save:success", @
|
||||
success(@, resp) if success
|
||||
success(@, res) if success
|
||||
@markToRevert()
|
||||
@clearBackup()
|
||||
options.error = (model, res) =>
|
||||
error(@, res) if error
|
||||
errorMessage = "Error saving #{@get('name') ? @type()}"
|
||||
console.error errorMessage, res.responseJSON
|
||||
noty text: "#{errorMessage}: #{res.status} #{res.statusText}", layout: 'topCenter', type: 'error', killer: false, timeout: 10000
|
||||
@trigger "save", @
|
||||
return super attrs, options
|
||||
|
||||
|
@ -164,7 +170,7 @@ class CocoModel extends Backbone.Model
|
|||
getDelta: ->
|
||||
differ = deltasLib.makeJSONDiffer()
|
||||
differ.diff @_revertAttributes, @attributes
|
||||
|
||||
|
||||
getDeltaWith: (otherModel) ->
|
||||
differ = deltasLib.makeJSONDiffer()
|
||||
differ.diff @attributes, otherModel.attributes
|
||||
|
|
|
@ -17,6 +17,16 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
# necessarily have the same model or collection that was passed in, if it was fetched from
|
||||
# the cache.
|
||||
|
||||
report: ->
|
||||
# Useful for debugging why a SuperModel never finishes loading.
|
||||
console.info "SuperModel report ------------------------"
|
||||
console.info "#{_.values(@resources).length} resources."
|
||||
unfinished = []
|
||||
for resource in _.values(@resources) when resource
|
||||
console.info '\t', resource.name, "loaded", resource.isLoaded
|
||||
unfinished.push resource unless resource.isLoaded
|
||||
unfinished
|
||||
|
||||
loadModel: (model, name, fetchOptions, value=1) ->
|
||||
cachedModel = @getModelByURL(model.getURL())
|
||||
if cachedModel
|
||||
|
@ -118,6 +128,9 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
@storeResource(res, value)
|
||||
return res
|
||||
|
||||
removeModelResource: (modelOrCollection) ->
|
||||
@removeResource _.find(@resources, (resource) -> resource?.model is modelOrCollection)
|
||||
|
||||
addRequestResource: (name, jqxhrOptions, value=1) ->
|
||||
@checkName(name)
|
||||
res = new RequestResource(name, jqxhrOptions, value)
|
||||
|
@ -143,11 +156,20 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
@denom += value
|
||||
_.defer @updateProgress if @denom
|
||||
|
||||
removeResource: (resource) ->
|
||||
return unless @resources[resource.rid]
|
||||
@resources[resource.rid] = null
|
||||
--@num if resource.isLoaded
|
||||
--@denom
|
||||
_.defer @updateProgress
|
||||
|
||||
onResourceLoaded: (r) ->
|
||||
return unless @resources[r.rid]
|
||||
@num += r.value
|
||||
_.defer @updateProgress
|
||||
|
||||
onResourceFailed: (source) ->
|
||||
return unless @resources[r.rid]
|
||||
@trigger('failed', source)
|
||||
|
||||
updateProgress: =>
|
||||
|
@ -160,13 +182,13 @@ module.exports = class SuperModel extends Backbone.Model
|
|||
@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) ->
|
||||
|
|
|
@ -62,7 +62,7 @@ module.exports = class ThangType extends CocoModel
|
|||
@options = @fillOptions options
|
||||
key = @spriteSheetKey(@options)
|
||||
if ss = @spriteSheets[key] then return ss
|
||||
return if @building[key]
|
||||
return key if @building[key]
|
||||
@t0 = new Date().getTime()
|
||||
@initBuild(options)
|
||||
@addGeneralFrames() unless @options.portraitOnly
|
||||
|
@ -151,25 +151,34 @@ module.exports = class ThangType extends CocoModel
|
|||
buildQueue.push @builder
|
||||
@builder.t0 = new Date().getTime()
|
||||
@builder.buildAsync() unless buildQueue.length > 1
|
||||
@builder.on 'complete', @onBuildSpriteSheetComplete, @, true, key
|
||||
return true
|
||||
@builder.on 'complete', @onBuildSpriteSheetComplete, @, true, [@builder, key, @options]
|
||||
@builder = null
|
||||
return key
|
||||
spriteSheet = @builder.build()
|
||||
console.debug "Built #{@get('name')}#{if @options.portraitOnly then ' portrait' else ''} in #{new Date().getTime() - @t0}ms."
|
||||
@logBuild @t0, false, @options.portraitOnly
|
||||
@spriteSheets[key] = spriteSheet
|
||||
delete @building[key]
|
||||
@builder = null
|
||||
spriteSheet
|
||||
|
||||
onBuildSpriteSheetComplete: (e, key) ->
|
||||
console.log "Built #{@get('name')}#{if @options.portraitOnly then ' portrait' else ''} async in #{new Date().getTime() - @builder.t0}ms." if @builder
|
||||
onBuildSpriteSheetComplete: (e, data) ->
|
||||
[builder, key, options] = data
|
||||
@logBuild builder.t0, true, options.portraitOnly
|
||||
buildQueue = buildQueue.slice(1)
|
||||
buildQueue[0].t0 = new Date().getTime() if buildQueue[0]
|
||||
buildQueue[0]?.buildAsync()
|
||||
@spriteSheets[key] = e.target.spriteSheet
|
||||
delete @building[key]
|
||||
@trigger 'build-complete'
|
||||
@builder = null
|
||||
@trigger 'build-complete', {key:key, thangType:@}
|
||||
@vectorParser = null
|
||||
|
||||
logBuild: (startTime, async, portrait) ->
|
||||
kind = if async then 'Async' else 'Sync '
|
||||
portrait = if portrait then '(Portrait)' else ''
|
||||
name = _.string.rpad @get('name'), 20
|
||||
time = _.string.lpad '' + new Date().getTime() - startTime, 6
|
||||
console.debug "Built sheet: #{name} #{time}ms #{kind} #{portrait}"
|
||||
|
||||
spriteSheetKey: (options) ->
|
||||
colorConfigs = []
|
||||
for groupName, config of options.colorConfig or {}
|
||||
|
@ -196,6 +205,7 @@ module.exports = class ThangType extends CocoModel
|
|||
options = if _.isPlainObject spriteOptionsOrKey then spriteOptionsOrKey else {}
|
||||
options.portraitOnly = true
|
||||
spriteSheet = @buildSpriteSheet(options)
|
||||
return if _.isString spriteSheet
|
||||
return unless spriteSheet
|
||||
canvas = $("<canvas width='#{size}' height='#{size}'></canvas>")
|
||||
stage = new createjs.Stage(canvas[0])
|
||||
|
|
|
@ -223,19 +223,19 @@ _.extend LevelSessionSchema.properties,
|
|||
sessionID:
|
||||
title: 'Opponent Session ID'
|
||||
description: 'The session ID of an opponent.'
|
||||
type: ['object', 'string']
|
||||
type: ['object', 'string','null']
|
||||
userID:
|
||||
title: 'Opponent User ID'
|
||||
description: 'The user ID of an opponent'
|
||||
type: ['object','string']
|
||||
type: ['object','string','null']
|
||||
name:
|
||||
title: 'Opponent name'
|
||||
description: 'The name of the opponent'
|
||||
type: 'string'
|
||||
type: ['string','null']
|
||||
totalScore:
|
||||
title: 'Opponent total score'
|
||||
description: 'The totalScore of a user when the match was computed'
|
||||
type: ['number','string']
|
||||
type: ['number','string', 'null']
|
||||
metrics:
|
||||
type: 'object'
|
||||
properties:
|
||||
|
|
|
@ -15,8 +15,21 @@ module.exports =
|
|||
type: "object"
|
||||
properties:
|
||||
response:
|
||||
type: "string"
|
||||
type: "object"
|
||||
properties:
|
||||
status: { type: "string" }
|
||||
authResponse:
|
||||
type: "object"
|
||||
properties:
|
||||
accessToken: { type: "string" }
|
||||
expiresIn: { type: "number" }
|
||||
signedRequest: { type: "string" }
|
||||
userID: { type: "string" }
|
||||
required: ["response"]
|
||||
|
||||
"facebook-logged-out": {}
|
||||
|
||||
"linkedin-loaded": {}
|
||||
|
||||
"gapi-loaded":
|
||||
{} # TODO schema
|
||||
|
|
|
@ -53,8 +53,9 @@ module.exports =
|
|||
"level:team-set":
|
||||
{} # TODO schema
|
||||
|
||||
"level:docs-hidden":
|
||||
{} # TODO schema
|
||||
"level:docs-shown": {}
|
||||
|
||||
"level:docs-hidden": {}
|
||||
|
||||
"level:victory-hidden":
|
||||
{} # TODO schema
|
||||
|
@ -74,9 +75,32 @@ module.exports =
|
|||
"script:ended":
|
||||
{} # TODO schema
|
||||
|
||||
"end-all-scripts": {}
|
||||
|
||||
"script:state-changed":
|
||||
{} # TODO schema
|
||||
|
||||
'script-manager:tick':
|
||||
type: 'object'
|
||||
additionalProperties: false
|
||||
properties:
|
||||
scriptRunning: { type: 'string' }
|
||||
noteGroupRunning: { type: 'string' }
|
||||
timeSinceLastScriptEnded: { type: 'number' }
|
||||
scriptStates:
|
||||
type: 'object'
|
||||
additionalProperties:
|
||||
title: 'Script State'
|
||||
type: 'object'
|
||||
additionalProperties: false
|
||||
properties:
|
||||
timeSinceLastEnded:
|
||||
type: 'number'
|
||||
description: 'seconds since this script ended last'
|
||||
timeSinceLastTriggered:
|
||||
type: 'number'
|
||||
description: 'seconds since this script was triggered last'
|
||||
|
||||
"play-sound":
|
||||
{} # TODO schema
|
||||
|
||||
|
@ -114,5 +138,21 @@ module.exports =
|
|||
"level-scrub-back":
|
||||
{} # TODO schema
|
||||
|
||||
"level-show-victory":
|
||||
type: 'object'
|
||||
additionalProperties: false
|
||||
properties:
|
||||
showModal: { type: 'boolean' }
|
||||
|
||||
"level-highlight-dom":
|
||||
type: 'object'
|
||||
additionalProperties: false
|
||||
properties:
|
||||
selector: { type: 'string' }
|
||||
delay: { type: 'number' }
|
||||
sides: { type: 'array', items: { 'enum': ['left', 'right', 'top', 'bottom'] }}
|
||||
offset: { type: 'object' }
|
||||
rotation: { type: 'number' }
|
||||
|
||||
"goal-manager:new-goal-states":
|
||||
{} # TODO schema
|
||||
|
|
|
@ -638,7 +638,7 @@ block content
|
|||
.tab-pane.well#rules
|
||||
h1(data-i18n="ladder.tournament_rules") Tournament Rules
|
||||
h2 General
|
||||
p You don't have to buy anything to participate in the tournament, and trying to pay us won't increase your odds of winning.
|
||||
p You don't have to buy anything to participate in the tournament, and trying to pay us won't increase your odds of winning. Although we don't anticipate the rules changing, they are subject to change.
|
||||
|
||||
h2 Dates and Times
|
||||
p The tournament starts on Tuesday, May 20 at 8:30AM and ends on Tuesday, June 10 at 5:00PM PDT. After the tournament finishes, we will check the games manually to prevent duplicate entries and cheating. We will email all the winners within two weeks of the end date.
|
||||
|
|
|
@ -39,11 +39,10 @@ module.exports = class ThangsTabView extends View
|
|||
'level-thang-edited': 'onLevelThangEdited'
|
||||
'level-thang-done-editing': 'onLevelThangDoneEditing'
|
||||
'level:view-switched': 'onViewSwitched'
|
||||
'sprite:mouse-down': 'onSpriteMouseDown'
|
||||
'sprite:dragged': 'onSpriteDragged'
|
||||
'sprite:mouse-up': 'onSpriteMouseUp'
|
||||
'sprite:double-clicked': 'onSpriteDoubleClicked'
|
||||
'surface:stage-mouse-down': 'onStageMouseDown'
|
||||
'surface:stage-mouse-up': 'onStageMouseUp'
|
||||
|
||||
events:
|
||||
'click #extant-thangs-filter button': 'onFilterExtantThangs'
|
||||
|
@ -108,7 +107,7 @@ module.exports = class ThangsTabView extends View
|
|||
afterRender: ->
|
||||
super()
|
||||
return unless @supermodel.finished()
|
||||
$('.tab-content').click @selectAddThang
|
||||
$('.tab-content').mousedown @selectAddThang
|
||||
$('#thangs-list').bind 'mousewheel', @preventBodyScrollingInThangList
|
||||
@$el.find('#extant-thangs-filter button:first').button('toggle')
|
||||
$(window).resize @onWindowResize
|
||||
|
@ -181,13 +180,13 @@ module.exports = class ThangsTabView extends View
|
|||
|
||||
onSpriteMouseDown: (e) ->
|
||||
# Sprite clicks happen after stage clicks, but we need to know whether a sprite is being clicked.
|
||||
clearTimeout @backgroundAddClickTimeout
|
||||
if e.originalEvent.nativeEvent.button == 2
|
||||
@onSpriteContextMenu e
|
||||
# clearTimeout @backgroundAddClickTimeout
|
||||
# if e.originalEvent.nativeEvent.button == 2
|
||||
# @onSpriteContextMenu e
|
||||
|
||||
onStageMouseDown: (e) ->
|
||||
onStageMouseUp: (e) ->
|
||||
if @addThangSprite
|
||||
# If we click on the background, we need to add @addThangSprite, but not if onSpriteMouseDown will fire.
|
||||
# If we click on the background, we need to add @addThangSprite, but not if onSpriteMouseUp will fire.
|
||||
@backgroundAddClickTimeout = _.defer => @onExtantThangSelected {}
|
||||
$('#contextmenu').hide()
|
||||
|
||||
|
@ -202,6 +201,9 @@ module.exports = class ThangsTabView extends View
|
|||
@calculateMovement(stageX / w, stageY / h, w / h)
|
||||
|
||||
onSpriteMouseUp: (e) ->
|
||||
clearTimeout @backgroundAddClickTimeout
|
||||
if e.originalEvent.nativeEvent.button == 2
|
||||
@onSpriteContextMenu e
|
||||
clearInterval(@movementInterval) if @movementInterval?
|
||||
@movementInterval = null
|
||||
@surface.camera.dragDisabled = false
|
||||
|
|
|
@ -72,6 +72,9 @@ module.exports = class LadderSubmissionView extends CocoView
|
|||
|
||||
transpileSession: ->
|
||||
submittedCode = @session.get('code')
|
||||
language = @session.get('codeLanguage') or 'javascript'
|
||||
@session.set('submittedCodeLanguage', language)
|
||||
@session.save() # TODO: maybe actually use a callback to make sure this works?
|
||||
transpiledCode = {}
|
||||
for thang, spells of submittedCode
|
||||
transpiledCode[thang] = {}
|
||||
|
@ -80,7 +83,7 @@ module.exports = class LadderSubmissionView extends CocoView
|
|||
#DRY this
|
||||
aetherOptions =
|
||||
problems: {}
|
||||
language: "javascript"
|
||||
language: language
|
||||
functionName: spellID
|
||||
functionParameters: []
|
||||
yieldConditionally: spellID is "plan"
|
||||
|
|
|
@ -150,9 +150,12 @@ module.exports = class LadderTabView extends CocoView
|
|||
# LADDER LOADING
|
||||
|
||||
refreshLadder: ->
|
||||
@supermodel.resetProgress()
|
||||
@ladderLimit ?= parseInt @getQueryVariable('top_players', 20)
|
||||
for team in @teams
|
||||
@leaderboards[team.id]?.destroy()
|
||||
if oldLeaderboard = @leaderboards[team.id]
|
||||
@supermodel.removeModelResource oldLeaderboard
|
||||
oldLeaderboard.destroy()
|
||||
teamSession = _.find @sessions.models, (session) -> session.get('team') is team.id
|
||||
@leaderboards[team.id] = new LeaderboardData(@level, team.id, teamSession, @ladderLimit)
|
||||
@leaderboardRes = @supermodel.addModelResource(@leaderboards[team.id], 'leaderboard', 3)
|
||||
|
@ -242,7 +245,7 @@ module.exports = class LadderTabView extends CocoView
|
|||
if teamName.toLowerCase() is "humans" then rankClass = "rank-text humans-rank-text"
|
||||
|
||||
message = "#{histogramData.length} players"
|
||||
if @leaderboards[teamName].session?
|
||||
if @leaderboards[teamName].session?
|
||||
if @leaderboards[teamName].myRank <= histogramData.length
|
||||
message="##{@leaderboards[teamName].myRank} of #{histogramData.length}"
|
||||
else
|
||||
|
|
|
@ -65,7 +65,7 @@ module.exports = class LadderView extends RootView
|
|||
@insertSubView(@simulateTab = new SimulateTabView())
|
||||
@refreshInterval = setInterval(@fetchSessionsAndRefreshViews.bind(@), 20 * 1000)
|
||||
hash = document.location.hash[1..] if document.location.hash
|
||||
if hash and not (hash in ['my-matches', 'simulate', 'ladder'])
|
||||
if hash and not (hash in ['my-matches', 'simulate', 'ladder', 'prizes', 'rules'])
|
||||
@showPlayModal(hash) if @sessions.loaded
|
||||
|
||||
fetchSessionsAndRefreshViews: ->
|
||||
|
|
|
@ -26,6 +26,9 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
for session in @sessions.models
|
||||
for match in (session.get('matches') or [])
|
||||
id = match.opponents[0].userID
|
||||
unless id
|
||||
console.error "Found bad opponent ID in malformed match:", match, "from session", session
|
||||
continue
|
||||
ids.push id unless @nameMap[id]
|
||||
|
||||
return @finishRendering() unless ids.length
|
||||
|
@ -35,7 +38,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
for session in @sessions.models
|
||||
for match in session.get('matches') or []
|
||||
opponent = match.opponents[0]
|
||||
@nameMap[opponent.userID] ?= nameMap[opponent.userID].name
|
||||
@nameMap[opponent.userID] ?= nameMap[opponent.userID]?.name ? "<bad match data>"
|
||||
@finishRendering()
|
||||
|
||||
$.ajax('/db/user/-/names', {
|
||||
|
@ -94,6 +97,7 @@ module.exports = class MyMatchesTabView extends CocoView
|
|||
|
||||
afterRender: ->
|
||||
super()
|
||||
@removeSubView subview for key, subview of @subviews when subview instanceof LadderSubmissionView
|
||||
@$el.find('.ladder-submission-view').each (i, el) =>
|
||||
placeholder = $(el)
|
||||
sessionID = placeholder.data('session-id')
|
||||
|
|
|
@ -13,6 +13,7 @@ module.exports = class DocsModal extends View
|
|||
'enter': 'hide'
|
||||
|
||||
constructor: (options) ->
|
||||
@firstOnly = options.firstOnly
|
||||
@docs = options?.docs
|
||||
general = @docs.generalArticles or []
|
||||
specific = @docs.specificArticles or []
|
||||
|
@ -25,6 +26,7 @@ module.exports = class DocsModal extends View
|
|||
|
||||
@docs = specific.concat(general)
|
||||
@docs = $.extend(true, [], @docs)
|
||||
@docs = [@docs[0]] if @firstOnly and @docs[0]
|
||||
doc.html = marked(utils.i18n doc, 'body') for doc in @docs
|
||||
doc.name = (utils.i18n doc, 'name') for doc in @docs
|
||||
doc.slug = _.string.slugify(doc.name) for doc in @docs
|
||||
|
@ -47,6 +49,10 @@ module.exports = class DocsModal extends View
|
|||
|
||||
clickTab: (e) =>
|
||||
@$el.find('li.active').removeClass('active')
|
||||
|
||||
afterInsert: ->
|
||||
super()
|
||||
Backbone.Mediator.publish 'level:docs-shown'
|
||||
|
||||
onHidden: ->
|
||||
Backbone.Mediator.publish 'level:docs-hidden'
|
||||
|
|
|
@ -21,8 +21,11 @@ module.exports = class ThangAvatarView extends View
|
|||
|
||||
unless @thangType.isFullyLoaded() or @thangType.loading
|
||||
@thangType.fetch()
|
||||
|
||||
@supermodel.loadModel @thangType, 'thang'
|
||||
|
||||
# couldn't get the level view to load properly through the supermodel
|
||||
# so just doing it manually this time.
|
||||
@listenTo @thangType, 'sync', @render
|
||||
@listenTo @thangType, 'build-complete', @render
|
||||
|
||||
getSpriteThangType: ->
|
||||
thangs = @supermodel.getModels(ThangType)
|
||||
|
@ -34,7 +37,7 @@ module.exports = class ThangAvatarView extends View
|
|||
context = super context
|
||||
context.thang = @thang
|
||||
options = @thang?.getSpriteOptions() or {}
|
||||
options.async = false
|
||||
options.async = true
|
||||
context.avatarURL = @thangType.getPortraitSource(options) unless @thangType.loading
|
||||
context.includeName = @includeName
|
||||
context
|
||||
|
|
|
@ -153,6 +153,11 @@ module.exports = class SpellView extends View
|
|||
name: 'spell-beautify'
|
||||
bindKey: {win: 'Ctrl-Shift-B', mac: 'Command-Shift-B|Ctrl-Shift-B'}
|
||||
exec: -> Backbone.Mediator.publish 'spell-beautify'
|
||||
addCommand
|
||||
name: 'prevent-line-jump'
|
||||
bindKey: {win: 'Ctrl-L', mac: 'Command-L'}
|
||||
passEvent: true
|
||||
exec: -> # just prevent default ACE go-to-line alert
|
||||
|
||||
fillACE: ->
|
||||
@ace.setValue @spell.source
|
||||
|
|
|
@ -133,7 +133,10 @@ module.exports = class PlayLevelView extends View
|
|||
showGuide: ->
|
||||
@seenDocs = true
|
||||
DocsModal = require './level/modal/docs_modal'
|
||||
options = {docs: @levelLoader.level.get('documentation'), supermodel: @supermodel}
|
||||
options =
|
||||
docs: @levelLoader.level.get('documentation')
|
||||
supermodel: @supermodel
|
||||
firstOnly: true
|
||||
@openModalView(new DocsModal(options), true)
|
||||
Backbone.Mediator.subscribeOnce 'modal-closed', @onLevelStarted, @
|
||||
return true
|
||||
|
@ -287,6 +290,7 @@ module.exports = class PlayLevelView extends View
|
|||
unless @isEditorPreview
|
||||
@loadEndTime = new Date()
|
||||
loadDuration = @loadEndTime - @loadStartTime
|
||||
console.debug "Level unveiled after #{(loadDuration / 1000).toFixed(2)}s"
|
||||
application.tracker?.trackEvent 'Finished Level Load', level: @levelID, label: @levelID, loadDuration: loadDuration
|
||||
application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ addPairwiseTaskToQueue = (taskPair, cb) ->
|
|||
if taskPairError? then return cb taskPairError
|
||||
cb null
|
||||
|
||||
# We should rip these out, probably
|
||||
module.exports.resimulateAllSessions = (req, res) ->
|
||||
unless isUserAdmin req then return errors.unauthorized res, "Unauthorized. Even if you are authorized, you shouldn't do this"
|
||||
|
||||
|
@ -99,6 +100,30 @@ resimulateSession = (originalLevelID, levelMajorVersion, session, cb) =>
|
|||
if taskPairError? then return cb taskPairError, null
|
||||
cb null
|
||||
|
||||
selectRandomSkipIndex = (numberOfSessions) ->
|
||||
numbers = [0...numberOfSessions]
|
||||
numberWeights = []
|
||||
lambda = 0.025
|
||||
|
||||
for number, index in numbers
|
||||
numberWeights[index] = lambda*Math.exp(-1*lambda*number) + lambda/(numberOfSessions/15)
|
||||
sum = numberWeights.reduce (a, b) -> a + b
|
||||
|
||||
for number,index in numberWeights
|
||||
numberWeights[index] /= sum
|
||||
|
||||
rand = (min, max) -> Math.random() * (max - min) + min
|
||||
|
||||
totalWeight = 1
|
||||
randomNumber = Math.random()
|
||||
weightSum = 0
|
||||
|
||||
for number, i in numbers
|
||||
weightSum += numberWeights[i]
|
||||
|
||||
if (randomNumber <= weightSum)
|
||||
return numbers[i]
|
||||
|
||||
module.exports.getTwoGames = (req, res) ->
|
||||
#if userIsAnonymous req then return errors.unauthorized(res, "You need to be logged in to get games.")
|
||||
humansGameID = req.body.humansGameID
|
||||
|
@ -106,53 +131,64 @@ module.exports.getTwoGames = (req, res) ->
|
|||
|
||||
unless ogresGameID and humansGameID
|
||||
#fetch random games here
|
||||
queryParams =
|
||||
queryParams =
|
||||
"levelID":"greed"
|
||||
"submitted":true
|
||||
"team":"humans"
|
||||
selection = "team totalScore transpiledCode teamSpells levelID creatorName creator"
|
||||
selection = "team totalScore transpiledCode teamSpells levelID creatorName creator submitDate"
|
||||
LevelSession.count queryParams, (err, numberOfHumans) =>
|
||||
query = LevelSession
|
||||
.find(queryParams)
|
||||
.limit(1)
|
||||
.select(selection)
|
||||
.skip(Math.floor(Math.random()*numberOfHumans))
|
||||
.lean()
|
||||
query.exec (err, randomSession) =>
|
||||
if err? then return errors.serverError(res, "Couldn't select a top 15 random session!")
|
||||
randomSession = randomSession[0]
|
||||
queryParams =
|
||||
"levelID":"greed"
|
||||
"submitted":true
|
||||
"totalScore":
|
||||
$lte: randomSession.totalScore
|
||||
"team": "ogres"
|
||||
if err? then return errors.serverError(res, "Couldn't get the number of human games")
|
||||
humanSkipCount = selectRandomSkipIndex(numberOfHumans)
|
||||
ogreCountParams =
|
||||
"levelID": "greed"
|
||||
"submitted":true
|
||||
"team":"ogres"
|
||||
LevelSession.count ogreCountParams, (err, numberOfOgres) =>
|
||||
if err? then return errors.serverError(res, "Couldnt' get the number of ogre games")
|
||||
ogresSkipCount = selectRandomSkipIndex(numberOfOgres)
|
||||
|
||||
query = LevelSession
|
||||
.find(queryParams)
|
||||
.select(selection)
|
||||
.sort(totalScore: -1)
|
||||
.aggregate()
|
||||
.match(queryParams)
|
||||
.project(selection)
|
||||
.sort({"submitDate": -1})
|
||||
.skip(humanSkipCount)
|
||||
.limit(1)
|
||||
.lean()
|
||||
query.exec (err, otherSession) =>
|
||||
if err? then return errors.serverError(res, "Couldnt' select the other top 15 random session!")
|
||||
otherSession = otherSession[0]
|
||||
taskObject =
|
||||
"messageGenerated": Date.now()
|
||||
"sessions": []
|
||||
for session in [randomSession, otherSession]
|
||||
sessionInformation =
|
||||
"sessionID": session._id
|
||||
"team": session.team ? "No team"
|
||||
"transpiledCode": session.transpiledCode
|
||||
"teamSpells": session.teamSpells ? {}
|
||||
"levelID": session.levelID
|
||||
"creatorName": session.creatorName
|
||||
"creator": session.creator
|
||||
"totalScore": session.totalScore
|
||||
|
||||
taskObject.sessions.push sessionInformation
|
||||
sendResponseObject req, res, taskObject
|
||||
query.exec (err, randomSession) =>
|
||||
if err? then return errors.serverError(res, "Couldn't select a random session! #{err}")
|
||||
randomSession = randomSession[0]
|
||||
queryParams =
|
||||
"levelID":"greed"
|
||||
"submitted":true
|
||||
"team": "ogres"
|
||||
query = LevelSession
|
||||
.aggregate()
|
||||
.match(queryParams)
|
||||
.project(selection)
|
||||
.sort({"submitDate": -1})
|
||||
.skip(ogresSkipCount)
|
||||
.limit(1)
|
||||
query.exec (err, otherSession) =>
|
||||
if err? then return errors.serverError(res, "Couldnt' select the other random session!")
|
||||
otherSession = otherSession[0]
|
||||
taskObject =
|
||||
"messageGenerated": Date.now()
|
||||
"sessions": []
|
||||
for session in [randomSession, otherSession]
|
||||
sessionInformation =
|
||||
"sessionID": session._id
|
||||
"team": session.team ? "No team"
|
||||
"transpiledCode": session.transpiledCode
|
||||
"teamSpells": session.teamSpells ? {}
|
||||
"levelID": session.levelID
|
||||
"creatorName": session.creatorName
|
||||
"creator": session.creator
|
||||
"totalScore": session.totalScore
|
||||
taskObject.sessions.push sessionInformation
|
||||
console.log "Dispatching random game between", taskObject.sessions[0].creatorName, "and", taskObject.sessions[1].creatorName
|
||||
sendResponseObject req, res, taskObject
|
||||
else
|
||||
console.log "Directly simulating #{humansGameID} vs. #{ogresGameID}."
|
||||
LevelSession.findOne(_id: humansGameID).lean().exec (err, humanSession) =>
|
||||
if err? then return errors.serverError(res, "Couldn't find the human game")
|
||||
LevelSession.findOne(_id: ogresGameID).lean().exec (err, ogreSession) =>
|
||||
|
@ -167,23 +203,25 @@ module.exports.getTwoGames = (req, res) ->
|
|||
"transpiledCode": session.transpiledCode
|
||||
"teamSpells": session.teamSpells ? {}
|
||||
"levelID": session.levelID
|
||||
|
||||
|
||||
taskObject.sessions.push sessionInformation
|
||||
sendResponseObject req, res, taskObject
|
||||
|
||||
module.exports.recordTwoGames = (req, res) ->
|
||||
@clientResponseObject = req.body
|
||||
|
||||
sessions = req.body.sessions
|
||||
console.log "Recording non-chained result of", sessions?[0]?.name, sessions[0]?.metrics?.rank, "and", sessions?[1]?.name, sessions?[1]?.metrics?.rank
|
||||
|
||||
yetiGuru = clientResponseObject: req.body, isRandomMatch: true
|
||||
async.waterfall [
|
||||
fetchLevelSession.bind(@)
|
||||
updateSessions.bind(@)
|
||||
indexNewScoreArray.bind(@)
|
||||
addMatchToSessions.bind(@)
|
||||
updateUserSimulationCounts.bind(@, req.user._id)
|
||||
fetchLevelSession.bind(yetiGuru)
|
||||
updateSessions.bind(yetiGuru)
|
||||
indexNewScoreArray.bind(yetiGuru)
|
||||
addMatchToSessions.bind(yetiGuru)
|
||||
updateUserSimulationCounts.bind(yetiGuru, req.user._id)
|
||||
], (err, successMessageObject) ->
|
||||
if err? then return errors.serverError res, "There was an error recording the single game:#{err}"
|
||||
sendResponseObject req, res, {"message":"The single game was submitted successfully!"}
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports.createNewTask = (req, res) ->
|
||||
|
@ -193,12 +231,13 @@ module.exports.createNewTask = (req, res) ->
|
|||
transpiledCode = req.body.transpiledCode
|
||||
requestLevelMajorVersion = parseInt(req.body.levelMajorVersion)
|
||||
|
||||
yetiGuru = {}
|
||||
async.waterfall [
|
||||
validatePermissions.bind(@,req,requestSessionID)
|
||||
fetchAndVerifyLevelType.bind(@,currentLevelID)
|
||||
fetchSessionObjectToSubmit.bind(@, requestSessionID)
|
||||
updateSessionToSubmit.bind(@, transpiledCode)
|
||||
fetchInitialSessionsToRankAgainst.bind(@, requestLevelMajorVersion, originalLevelID)
|
||||
validatePermissions.bind(yetiGuru,req,requestSessionID)
|
||||
fetchAndVerifyLevelType.bind(yetiGuru,currentLevelID)
|
||||
fetchSessionObjectToSubmit.bind(yetiGuru, requestSessionID)
|
||||
updateSessionToSubmit.bind(yetiGuru, transpiledCode)
|
||||
fetchInitialSessionsToRankAgainst.bind(yetiGuru, requestLevelMajorVersion, originalLevelID)
|
||||
generateAndSendTaskPairsToTheQueue
|
||||
], (err, successMessageObject) ->
|
||||
if err? then return errors.serverError res, "There was an error submitting the game to the queue:#{err}"
|
||||
|
@ -256,9 +295,9 @@ updateSessionToSubmit = (transpiledCode, sessionToUpdate, callback) ->
|
|||
submittedCode: sessionToUpdate.code
|
||||
transpiledCode: transpiledCode
|
||||
submitDate: new Date()
|
||||
meanStrength: 25
|
||||
#meanStrength: 25 # Let's try not resetting the score on resubmission
|
||||
standardDeviation: 25/3
|
||||
totalScore: 10
|
||||
#totalScore: 10 # Let's try not resetting the score on resubmission
|
||||
numberOfWinsAndTies: 0
|
||||
numberOfLosses: 0
|
||||
isRanking: true
|
||||
|
@ -300,13 +339,14 @@ generateAndSendTaskPairsToTheQueue = (sessionToRankAgainst,submittedSession, cal
|
|||
|
||||
|
||||
module.exports.dispatchTaskToConsumer = (req, res) ->
|
||||
yetiGuru = {}
|
||||
async.waterfall [
|
||||
checkSimulationPermissions.bind(@,req)
|
||||
checkSimulationPermissions.bind(yetiGuru,req)
|
||||
receiveMessageFromSimulationQueue
|
||||
changeMessageVisibilityTimeout
|
||||
parseTaskQueueMessage
|
||||
constructTaskObject
|
||||
constructTaskLogObject.bind(@, getUserIDFromRequest(req))
|
||||
constructTaskLogObject.bind(yetiGuru, getUserIDFromRequest(req))
|
||||
processTaskObject
|
||||
], (err, taskObjectToSend) ->
|
||||
if err?
|
||||
|
@ -357,7 +397,6 @@ constructTaskObject = (taskMessageBody, message, callback) ->
|
|||
"sessionID": session._id
|
||||
"submitDate": session.submitDate
|
||||
"team": session.team ? "No team"
|
||||
"code": session.submittedCode
|
||||
"transpiledCode": session.transpiledCode
|
||||
"teamSpells": session.teamSpells ? {}
|
||||
"levelID": session.levelID
|
||||
|
@ -397,34 +436,38 @@ getSessionInformation = (sessionIDString, callback) ->
|
|||
|
||||
module.exports.processTaskResult = (req, res) ->
|
||||
originalSessionID = req.body?.originalSessionID
|
||||
async.waterfall [
|
||||
verifyClientResponse.bind(@,req.body)
|
||||
fetchTaskLog.bind(@)
|
||||
checkTaskLog.bind(@)
|
||||
deleteQueueMessage.bind(@)
|
||||
fetchLevelSession.bind(@)
|
||||
checkSubmissionDate.bind(@)
|
||||
logTaskComputation.bind(@)
|
||||
updateSessions.bind(@)
|
||||
indexNewScoreArray.bind(@)
|
||||
addMatchToSessions.bind(@)
|
||||
updateUserSimulationCounts.bind(@, req.user._id)
|
||||
determineIfSessionShouldContinueAndUpdateLog.bind(@)
|
||||
findNearestBetterSessionID.bind(@)
|
||||
addNewSessionsToQueue.bind(@)
|
||||
], (err, results) ->
|
||||
if err is "shouldn't continue"
|
||||
markSessionAsDoneRanking originalSessionID, (err) ->
|
||||
if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"}
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
|
||||
else if err is "no session was found"
|
||||
markSessionAsDoneRanking originalSessionID, (err) ->
|
||||
if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"}
|
||||
sendResponseObject req, res, {"message":"There were no more games to rank (game is at top)!"}
|
||||
else if err?
|
||||
errors.serverError res, "There was an error:#{err}"
|
||||
else
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
|
||||
yetiGuru = {}
|
||||
try
|
||||
async.waterfall [
|
||||
verifyClientResponse.bind(yetiGuru,req.body)
|
||||
fetchTaskLog.bind(yetiGuru)
|
||||
checkTaskLog.bind(yetiGuru)
|
||||
deleteQueueMessage.bind(yetiGuru)
|
||||
fetchLevelSession.bind(yetiGuru)
|
||||
checkSubmissionDate.bind(yetiGuru)
|
||||
logTaskComputation.bind(yetiGuru)
|
||||
updateSessions.bind(yetiGuru)
|
||||
indexNewScoreArray.bind(yetiGuru)
|
||||
addMatchToSessions.bind(yetiGuru)
|
||||
updateUserSimulationCounts.bind(yetiGuru, req.user._id)
|
||||
determineIfSessionShouldContinueAndUpdateLog.bind(yetiGuru)
|
||||
findNearestBetterSessionID.bind(yetiGuru)
|
||||
addNewSessionsToQueue.bind(yetiGuru)
|
||||
], (err, results) ->
|
||||
if err is "shouldn't continue"
|
||||
markSessionAsDoneRanking originalSessionID, (err) ->
|
||||
if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"}
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
|
||||
else if err is "no session was found"
|
||||
markSessionAsDoneRanking originalSessionID, (err) ->
|
||||
if err? then return sendResponseObject req, res, {"error":"There was an error marking the session as done ranking"}
|
||||
sendResponseObject req, res, {"message":"There were no more games to rank (game is at top)!"}
|
||||
else if err?
|
||||
errors.serverError res, "There was an error:#{err}"
|
||||
else
|
||||
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
|
||||
catch e
|
||||
errors.serverError res, "There was an error processing the task result!"
|
||||
|
||||
verifyClientResponse = (responseObject, callback) ->
|
||||
#TODO: better verification
|
||||
|
@ -432,6 +475,7 @@ verifyClientResponse = (responseObject, callback) ->
|
|||
callback "The response to that query is required to be a JSON object."
|
||||
else
|
||||
@clientResponseObject = responseObject
|
||||
|
||||
#log.info "Verified client response!"
|
||||
callback null, responseObject
|
||||
|
||||
|
@ -459,6 +503,7 @@ deleteQueueMessage = (callback) ->
|
|||
fetchLevelSession = (callback) ->
|
||||
findParameters =
|
||||
_id: @clientResponseObject.originalSessionID
|
||||
|
||||
query = LevelSession
|
||||
.findOne(findParameters)
|
||||
.lean()
|
||||
|
@ -490,11 +535,12 @@ updateSessions = (callback) ->
|
|||
|
||||
async.map sessionIDs, retrieveOldSessionData, (err, oldScores) =>
|
||||
if err? then callback err, {"error": "There was an error retrieving the old scores"}
|
||||
|
||||
oldScoreArray = _.toArray putRankingFromMetricsIntoScoreObject @clientResponseObject, oldScores
|
||||
newScoreArray = bayes.updatePlayerSkills oldScoreArray
|
||||
saveNewScoresToDatabase newScoreArray, callback
|
||||
|
||||
try
|
||||
oldScoreArray = _.toArray putRankingFromMetricsIntoScoreObject @clientResponseObject, oldScores
|
||||
newScoreArray = bayes.updatePlayerSkills oldScoreArray
|
||||
saveNewScoresToDatabase newScoreArray, callback
|
||||
catch e
|
||||
callback e
|
||||
|
||||
saveNewScoresToDatabase = (newScoreArray, callback) ->
|
||||
async.eachSeries newScoreArray, updateScoreInSession, (err) ->
|
||||
|
@ -541,7 +587,8 @@ addMatchToSessions = (newScoreObject, callback) ->
|
|||
#log.info "Writing match object to database..."
|
||||
#use bind with async to do the writes
|
||||
sessionIDs = _.pluck @clientResponseObject.sessions, 'sessionID'
|
||||
async.each sessionIDs, updateMatchesInSession.bind(@,matchObject), (err) -> callback err
|
||||
async.each sessionIDs, updateMatchesInSession.bind(@,matchObject), (err) ->
|
||||
callback err
|
||||
|
||||
updateMatchesInSession = (matchObject, sessionID, callback) ->
|
||||
currentMatchObject = {}
|
||||
|
@ -562,7 +609,11 @@ updateMatchesInSession = (matchObject, sessionID, callback) ->
|
|||
updateUserSimulationCounts = (reqUserID,callback) ->
|
||||
incrementUserSimulationCount reqUserID, 'simulatedBy', (err) =>
|
||||
if err? then return callback err
|
||||
incrementUserSimulationCount @levelSession.creator, 'simulatedFor', callback
|
||||
console.log "Incremented user simulation count!"
|
||||
unless @isRandomMatch
|
||||
incrementUserSimulationCount @levelSession.creator, 'simulatedFor', callback
|
||||
else
|
||||
callback null
|
||||
|
||||
incrementUserSimulationCount = (userID, type, callback) =>
|
||||
inc = {}
|
||||
|
@ -605,13 +656,16 @@ determineIfSessionShouldContinueAndUpdateLog = (cb) ->
|
|||
|
||||
|
||||
findNearestBetterSessionID = (cb) ->
|
||||
levelOriginalID = @levelSession.level.original
|
||||
levelMajorVersion = @levelSession.level.majorVersion
|
||||
sessionID = @clientResponseObject.originalSessionID
|
||||
sessionTotalScore = @newScoresObject[sessionID].totalScore
|
||||
opponentSessionID = _.pull(_.keys(@newScoresObject), sessionID)
|
||||
opponentSessionTotalScore = @newScoresObject[opponentSessionID].totalScore
|
||||
opposingTeam = calculateOpposingTeam(@clientResponseObject.originalSessionTeam)
|
||||
try
|
||||
levelOriginalID = @levelSession.level.original
|
||||
levelMajorVersion = @levelSession.level.majorVersion
|
||||
sessionID = @clientResponseObject.originalSessionID
|
||||
sessionTotalScore = @newScoresObject[sessionID].totalScore
|
||||
opponentSessionID = _.pull(_.keys(@newScoresObject), sessionID)
|
||||
opponentSessionTotalScore = @newScoresObject[opponentSessionID].totalScore
|
||||
opposingTeam = calculateOpposingTeam(@clientResponseObject.originalSessionTeam)
|
||||
catch e
|
||||
cb e
|
||||
|
||||
retrieveAllOpponentSessionIDs sessionID, (err, opponentSessionIDs) ->
|
||||
if err? then return cb err, null
|
||||
|
@ -708,7 +762,7 @@ hasTaskTimedOut = (taskSentTimestamp) -> taskSentTimestamp + scoringTaskTimeoutI
|
|||
|
||||
handleTimedOutTask = (req, res, taskBody) -> errors.clientTimeout res, "The results weren't provided within the timeout"
|
||||
|
||||
putRankingFromMetricsIntoScoreObject = (taskObject,scoreObject) ->
|
||||
putRankingFromMetricsIntoScoreObject = (taskObject, scoreObject) ->
|
||||
scoreObject = _.indexBy scoreObject, 'id'
|
||||
scoreObject[session.sessionID].gameRanking = session.metrics.rank for session in taskObject.sessions
|
||||
return scoreObject
|
||||
|
|
|
@ -22,7 +22,7 @@ productionLogging = (tokens, req, res) ->
|
|||
else if status >= 300 then color = 36
|
||||
elapsed = (new Date()) - req._startTime
|
||||
elapsedColor = if elapsed < 500 then 90 else 31
|
||||
if (status isnt 200 and status isnt 304 and status isnt 302) or elapsed > 500
|
||||
if (status isnt 200 and status isnt 204 and status isnt 304 and status isnt 302) or elapsed > 500
|
||||
return "\x1b[90m#{req.method} #{req.originalUrl} \x1b[#{color}m#{res.statusCode} \x1b[#{elapsedColor}m#{elapsed}ms\x1b[0m"
|
||||
null
|
||||
|
||||
|
@ -92,7 +92,7 @@ sendMain = (req, res) ->
|
|||
fs.readFile path.join(__dirname, 'public', 'main.html'), 'utf8', (err, data) ->
|
||||
log.error "Error modifying main.html: #{err}" if err
|
||||
# insert the user object directly into the html so the application can have it immediately. Sanitize </script>
|
||||
data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace('/', '\\/'))
|
||||
data = data.replace('"userObjectTag"', JSON.stringify(UserHandler.formatEntity(req, req.user)).replace(/\//g, '\\/'))
|
||||
res.send data
|
||||
|
||||
setupFacebookCrossDomainCommunicationRoute = (app) ->
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue