This commit is contained in:
Scott Erickson 2014-05-06 11:21:15 -07:00
commit c58a27fcbf
80 changed files with 1091 additions and 118 deletions

11
.gitignore vendored
View file

@ -28,6 +28,9 @@ Thumbs.db
*.sublime-project
*.sublime-workspace
# IntelliJ/WebStorm
*.iml
# NPM packages folder.
node_modules/
bower_components/
@ -77,4 +80,10 @@ bin/mongo/
# windows
/SCOCODE.bat
### If you add something here, copy it to the end of .npmignore, too. ###
# local settings
login.coffee
# debugging
*.heapsnapshot
### If you add something here, copy it to the end of .npmignore, too. ###

View file

@ -53,6 +53,9 @@ Thumbs.db
*.sublime-project
*.sublime-workspace
# IntelliJ/WebStorm
*.iml
# NPM packages folder.
node_modules/
@ -89,6 +92,12 @@ mongo/
bin/node/
bin/mongo/
# Karma coverage
coverage/
# local settings
login.coffee
# debugging
*.heapsnapshot

241
app/lib/Buddha.coffee Normal file
View file

@ -0,0 +1,241 @@
#Sane rewrite of God (a thread pool)
{now} = require 'lib/world/world_utils'
World = require 'lib/world/world'
###
Every Angel has exactly one WebWorker attached to it.
It will call methods inside the webwrker and kill it if it times out.
###
class Angel
@cyanide: 0xDEADBEEF
infiniteLoopIntervalDuration: 7500 # check this often (must be more than the others added)
infiniteLoopTimeoutDuration: 10000 # wait this long when we check
abortTimeoutDuration: 500 # give in-process or dying workers this long to give up
constructor: (@id, @shared) ->
console.log @id + ": Creating Angel"
if (navigator.userAgent or navigator.vendor or window.opera).search("MSIE") isnt -1
@infiniteLoopIntervalDuration *= 20 # since it's so slow to serialize without transferable objects, we can't trust it
@infiniteLoopTimeoutDuration *= 20
@abortTimeoutDuration *= 10
@initialized = false
@running = false
@hireWorker()
@shared.angels.push @
testWorker: =>
if @initialized
@worker.postMessage {func: 'reportIn'}
# Are there any errors when webworker isn't loaded properly?
onWorkerMessage: (event) =>
#console.log JSON.stringify event
if @aborting and not
event.data.type is 'abort'
console.log id + " is currently aborting old work."
return
switch event.data.type
when 'start-load-frames'
clearTimeout(@condemnTimeout)
@condemnTimeout = _.delay @infinitelyLooped, @infiniteLoopTimeoutDuration
when 'end-load-frames'
console.log @id + ': No condemn this time.'
clearTimeout(@condemnTimeout)
when 'worker-initialized'
unless @initialized
console.log @id + ": Worker initialized after", ((new Date()) - @worker.creationTime), "ms"
@initialized = true
@doWork()
when 'new-world'
@beholdWorld event.data.serialized, event.data.goalStates
when 'world-load-progress-changed'
Backbone.Mediator.publish 'god:world-load-progress-changed', event.data
when 'console-log'
console.log "|" + @id + "|", event.data.args...
when 'user-code-problem'
Backbone.Mediator.publish 'god:user-code-problem', problem: event.data.problem
when 'abort'
console.log @id, "aborted."
clearTimeout @abortTimeout
@aborting = false
@running = false
@shared.busyAngels.pop @
@doWork()
when 'reportIn'
clearTimeout @condemnTimeout
else
console.log @id + " received unsupported message:", event.data
beholdWorld: (serialized, goalStates) ->
return if @aborting
unless serialized
# We're only interested in goalStates. (Simulator)
@latestGoalStates = goalStates
Backbone.Mediator.publish('god:goals-calculated', goalStates: goalStates)
@running = false
@shared.busyAngels.pop @
# console.warn "Goal states: " + JSON.stringify(goalStates)
window.BOX2D_ENABLED = false # Flip this off so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment
World.deserialize serialized, @shared.worldClassMap, @lastSerializedWorldFrames, @finishBeholdingWorld(goalStates)
window.BOX2D_ENABLED = true
@lastSerializedWorldFrames = serialized.frames
finishBeholdingWorld: (goalStates) => (world) =>
return if @aborting
world.findFirstChangedFrame @shared.world
@shared.world = world
errorCount = (t for t in @shared.world.thangs when t.errorsOut).length
Backbone.Mediator.publish('god:new-world-created', world: world, firstWorld: @shared.firstWorld, errorCount: errorCount, goalStates: goalStates)
for scriptNote in @shared.world.scriptNotes
Backbone.Mediator.publish scriptNote.channel, scriptNote.event
@shared.goalManager?.world = world
@running = false
@shared.busyAngels.pop @
@shared.firstWorld = false
@doWork()
infinitelyLooped: =>
unless @aborting
problem = type: "runtime", level: "error", id: "runtime_InfiniteLoop", message: "Code never finished. It's either really slow or has an infinite loop."
Backbone.Mediator.publish 'god:user-code-problem', problem: problem
Backbone.Mediator.publish 'god:infinite-loop', firstWorld: @shared.firstWorld
@fireWorker()
workIfIdle: ->
@doWork() unless @running
doWork: =>
#console.log "work."
return if @aborted
console.log @id + " ready and looking for work. WorkQueue length is " + @shared.workQueue.length
if @initialized and @shared.workQueue.length
work = @shared.workQueue.pop()
if work is Angel.cyanide # Kill all other Angels, too
console.log @id + ": 'work is poison'"
@shared.workQueue.push Angel.cyanide
@free()
else
console.log @id + ": Sending the worker to work."
@running = true
@shared.busyAngels.push @
console.log "Running world..."
#console.error "worker.postMessage: " + @worker.postMessage + ", work: " + work
@worker.postMessage func: 'runWorld', args: work
console.log @id + ": Setting interval."
clearTimeout @purgatoryTimer
@purgatoryTimer = setInterval @testWorker, @infiniteLoopIntervalDuration
else
console.log "No work for " + @id
@hireWorker()
abort: =>
if @worker and @running
console.log "Aborting " + @id
@running = false
@shared.busyAngels.pop @
@abortTimeout = _.delay @terminate, @fireWorker, @abortTimeoutDuration
@worker.postMessage func: 'abort'
@aborting = true
@work = null
fireWorker: (rehire=true) =>
@aborting = false
@running = false
@shared.busyAngels.pop @
@worker?.removeEventListener 'message', @onWorkerMessage
@worker?.terminate()
@worker = null
clearTimeout @condemnTimeout
clearInterval @purgatoryTimer
console.log "Fired worker."
@initialized = false
@work = null
@hireWorker() if rehire
hireWorker: ->
unless @worker
console.log @id + ": Hiring worker."
@worker = new Worker @shared.workerCode
@worker.addEventListener 'message', @onWorkerMessage
@worker.creationTime = new Date()
#@worker.postMessage func: 'initialized' else
kill: ->
@fireWorker false
@shared.angels.pop @
clearTimeout @condemnTimeout
clearTimeout @purgatoryTimer
@purgatoryTimer = null
@condemnTimeout = null
module.exports = class God
ids: ['Athena', 'Baldr', 'Crom', 'Dagr', 'Eris', 'Freyja', 'Great Gish', 'Hades', 'Ishtar', 'Janus', 'Khronos', 'Loki', 'Marduk', 'Negafook', 'Odin', 'Poseidon', 'Quetzalcoatl', 'Ra', 'Shiva', 'Thor', 'Umvelinqangi', 'Týr', 'Vishnu', 'Wepwawet', 'Xipe Totec', 'Yahweh', 'Zeus', '上帝', 'Tiamat', '盘古', 'Phoebe', 'Artemis', 'Osiris', "嫦娥", 'Anhur', 'Teshub', 'Enlil', 'Perkele', 'Chaos', 'Hera', 'Iris', 'Theia', 'Uranus', 'Stribog', 'Sabazios', 'Izanagi', 'Ao', 'Tāwhirimātea', 'Tengri', 'Inmar', 'Torngarsuk', 'Centzonhuitznahua', 'Hunab Ku', 'Apollo', 'Helios', 'Thoth', 'Hyperion', 'Alectrona', 'Eos', 'Mitra', 'Saranyu', 'Freyr', 'Koyash', 'Atropos', 'Clotho', 'Lachesis', 'Tyche', 'Skuld', 'Urðr', 'Verðandi', 'Camaxtli', 'Huhetotl', 'Set', 'Anu', 'Allah', 'Anshar', 'Hermes', 'Lugh', 'Brigit', 'Manannan Mac Lir', 'Persephone', 'Mercury', 'Venus', 'Mars', 'Azrael', 'He-Man', 'Anansi', 'Issek', 'Mog', 'Kos', 'Amaterasu Omikami', 'Raijin', 'Susanowo', 'Blind Io', 'The Lady', 'Offler', 'Ptah', 'Anubis', 'Ereshkigal', 'Nergal', 'Thanatos', 'Macaria', 'Angelos', 'Erebus', 'Hecate', 'Hel', 'Orcus', 'Ishtar-Deela Nakh', 'Prometheus', 'Hephaestos', 'Sekhmet', 'Ares', 'Enyo', 'Otrera', 'Pele', 'Hadúr', 'Hachiman', 'Dayisun Tngri', 'Ullr', 'Lua', 'Minerva']
nextID: ->
@lastID = (if @lastID? then @lastID + 1 else Math.floor(@ids.length * Math.random())) % @ids.length
@ids[@lastID]
# Charlie's Angels are all given access to this.
angelsShare: {
workerCode: '/javascripts/workers/worker_world.js' # Either path or function
workQueue: []
firstWorld: true
world: undefined
goalManager: undefined
worldClassMap: undefined
angels: []
busyAngels: [] # Busy angels will automatically register here.
}
constructor: (options) ->
options ?= {}
@angelsShare.workerCode = options.workerCode if options.workerCode
# ~20MB per idle worker + angel overhead - in this implementation, every Angel maps to 1 worker
angelCount = options.maxAngels ? options.maxWorkerPoolSize ? 2 # How many concurrent Angels/web workers to use at a time
_.delay (=>new Angel @nextID(), @angelsShare), 250 * i for i in [0...angelCount] # Don't generate all Angels at once.
Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
onTomeCast: (e) ->
@createWorld e.spells
setGoalManager: (goalManager) =>
@angelsShare.goalManager = goalManager
setWorldClassMap: (worldClassMap) =>
@angelsShare.worldClassMap = worldClassMap
getUserCodeMap: (spells) ->
userCodeMap = {}
for spellKey, spell of spells
for thangID, spellThang of spell.thangs
(userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
#console.log userCodeMap
userCodeMap
createWorld: (spells) =>
angel.abort() for angel in @angelsShare.busyAngels # We really only ever want one world calculated per God
#console.log "Level: " + @level
@angelsShare.workQueue.push
worldName: @level.name
userCodeMap: @getUserCodeMap(spells)
level: @level
goals: @angelsShare.goalManager?.getGoals()
angel.workIfIdle() for angel in @angelsShare.angels
destroy: =>
console.log "Destroying Buddha"
@createWorld = -> console.log "CreateWorld already gone."
@angelsShare.workQueue.push Angel.cyanide
angel.kill for angel in @angelsShare.busyAngels
Backbone.Mediator.unsubscribe('tome:cast-spells', @onTomeCast, @)
@angelsShare.goalManager?.destroy()
@angelsShare.goalManager = null
@angelsShare = null

View file

@ -18,16 +18,18 @@ module.exports = class God
options ?= {}
@maxAngels = options.maxAngels ? 2 # How many concurrent web workers to use; if set past 8, make up more names
@maxWorkerPoolSize = options.maxWorkerPoolSize ? 2 # ~20MB per idle worker
@workerCode = options.workerCode if options.workerCode?
@angels = []
@firstWorld = true
Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
@fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false
@fillWorkerPool()
workerCode: '/javascripts/workers/worker_world.js' #Can be a string or a function.
onTomeCast: (e) ->
return if @dead
@spells = e.spells
@createWorld()
@createWorld e.spells
fillWorkerPool: =>
return unless Worker and not @dead
@ -44,17 +46,21 @@ module.exports = class God
@createWorker()
createWorker: ->
worker = new Worker '/javascripts/workers/worker_world.js'
worker = new Worker @workerCode
worker.creationTime = new Date()
worker.addEventListener 'message', @onWorkerMessage
worker.addEventListener 'message', @onWorkerMessage(worker)
worker
onWorkerMessage: (event) =>
worker = event.target
if event.data.type is 'worker-initialized'
#console.log @id, "worker initialized after", ((new Date()) - worker.creationTime), "ms (before it was needed)"
worker.initialized = true
worker.removeEventListener 'message', @onWorkerMessage
onWorkerMessage: (worker) =>
unless worker.onMessage?
worker.onMessage = (event) =>
if event.data.type is 'worker-initialized'
console.log @id, "worker initialized after", ((new Date()) - worker.creationTime), "ms (before it was needed)"
worker.initialized = true
worker.removeEventListener 'message', worker.onMessage
else
console.warn "Received strange word from God: #{event.data.type}"
worker.onMessage
getAngel: ->
freeAngel = null
@ -86,7 +92,7 @@ module.exports = class God
#console.log "UserCodeProblem:", '"' + problem.message + '"', "for", problem.userInfo.thangID, "-", problem.userInfo.methodName, 'at line', problem.ranges?[0][0][0], 'column', problem.ranges?[0][0][1]
Backbone.Mediator.publish 'god:user-code-problem', problem: problem
createWorld: ->
createWorld: (@spells) ->
#console.log @id + ': "Let there be light upon', @world.name + '!"'
unless Worker? # profiling world simulation is easier on main thread, or we are IE9
setTimeout @simulateWorld, 1
@ -101,20 +107,37 @@ module.exports = class God
#console.log "going to run world with code", @getUserCodeMap()
angel.worker.postMessage {func: 'runWorld', args: {
worldName: @level.name
userCodeMap: @getUserCodeMap()
userCodeMap: @getUserCodeMap(spells)
level: @level
firstWorld: @firstWorld
goals: @goalManager?.getGoals()
}}
#Coffeescript needs getters and setters.
setGoalManager: (@goalManager) =>
setWorldClassMap: (@worldClassMap) =>
beholdWorld: (angel, serialized, goalStates) ->
unless serialized
# We're only interested in goalStates.
@latestGoalStates = goalStates
Backbone.Mediator.publish('god:goals-calculated', goalStates: goalStates, team: me.team)
unless _.find @angels, 'busy'
@spells = null # Don't hold onto old spells; memory leaks
return
console.log "Beholding world."
worldCreation = angel.started
angel.free()
return if @latestWorldCreation? and worldCreation < @latestWorldCreation
@latestWorldCreation = worldCreation
@latestGoalStates = goalStates
console.warn "Goal states: " + JSON.stringify(goalStates)
window.BOX2D_ENABLED = false # Flip this off so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment
World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, worldCreation, @finishBeholdingWorld
World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, @finishBeholdingWorld
window.BOX2D_ENABLED = true
@lastSerializedWorldFrames = serialized.frames
@ -171,7 +194,7 @@ module.exports = class God
@latestGoalStates = @testGM?.getGoalStates()
serialized = @testWorld.serialize().serializedWorld
window.BOX2D_ENABLED = false
World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, @t0, @finishBeholdingWorld
World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, @finishBeholdingWorld
window.BOX2D_ENABLED = true
@lastSerializedWorldFrames = serialized.frames
@ -255,7 +278,7 @@ class Angel
testWorker: =>
unless @worker.initialized
console.warn "Worker", @id, "hadn't even loaded the scripts yet after", @infiniteLoopIntervalDuration, "ms."
console.warn "Worker", @id, " hadn't even loaded the scripts yet after", @infiniteLoopIntervalDuration, "ms."
return
@worker.postMessage {func: 'reportIn'}
@condemnTimeout = _.delay @condemnWorker, @infiniteLoopTimeoutDuration
@ -271,6 +294,7 @@ class Angel
switch event.data.type
when 'worker-initialized'
console.log "Worker", @id, "initialized after", ((new Date()) - @worker.creationTime), "ms (we had been waiting for it)"
@worker.initialized = true
when 'new-world'
@god.beholdWorld @, event.data.serialized, event.data.goalStates
when 'world-load-progress-changed'

View file

@ -155,6 +155,7 @@ module.exports = class LevelLoader extends CocoClass
# Building sprite sheets
buildSpriteSheetsForThangType: (thangType) ->
return if @headless
@grabThangTypeTeams() unless @thangTypeTeams
for team in @thangTypeTeams[thangType.get('original')] ? [null]
spriteOptions = {resolutionFactor: 4, async: false}

View file

@ -2,15 +2,18 @@ SuperModel = require 'models/SuperModel'
CocoClass = require 'lib/CocoClass'
LevelLoader = require 'lib/LevelLoader'
GoalManager = require 'lib/world/GoalManager'
God = require 'lib/God'
God = require 'lib/Buddha'
module.exports = class Simulator extends CocoClass
constructor: ->
constructor: (@options) ->
@options ?= {}
_.extend @, Backbone.Events
@trigger 'statusUpdate', 'Starting simulation!'
@retryDelayInSeconds = 10
@taskURL = '/queue/scoring'
@simulatedByYou = 0
@god = new God maxWorkerPoolSize: 1, maxAngels: 1, workerCode: @options.workerCode # Start loading worker.
destroy: ->
@off()
@ -19,6 +22,17 @@ module.exports = class Simulator extends CocoClass
fetchAndSimulateTask: =>
return if @destroyed
if @options.headlessClient
if @dumpThisTime # The first heapdump would be useless to find leaks.
console.log "Writing snapshot."
@options.heapdump.writeSnapshot()
@dumpThisTime = true if @options.heapdump
if @options.testing
_.delay @setupSimulationAndLoadLevel, 0, @options.testFile, "Testing...", status: 400
return
@trigger 'statusUpdate', 'Fetching simulation data!'
$.ajax
url: @taskURL
@ -32,7 +46,9 @@ module.exports = class Simulator extends CocoClass
@simulateAnotherTaskAfterDelay()
handleNoGamesResponse: ->
@trigger 'statusUpdate', 'There were no games to simulate--all simulations are done or in process. Retrying in 10 seconds.'
info = 'There were no games to simulate--all simulations are done or in process. Retrying in 10 seconds.'
console.log info
@trigger 'statusUpdate', info
@simulateAnotherTaskAfterDelay()
simulateAnotherTaskAfterDelay: =>
@ -53,7 +69,6 @@ module.exports = class Simulator extends CocoClass
return
@supermodel ?= new SuperModel()
@god = new God maxWorkerPoolSize: 1, maxAngels: 1 # Start loading worker.
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: levelID, sessionID: @task.getFirstSessionID(), headless: true
if @supermodel.finished()
@ -63,7 +78,9 @@ module.exports = class Simulator extends CocoClass
simulateGame: ->
return if @destroyed
@trigger 'statusUpdate', 'All resources loaded, simulating!', @task.getSessions()
info = 'All resources loaded, simulating!'
console.log info
@trigger 'statusUpdate', info, @task.getSessions()
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
@setupGod()
@ -74,6 +91,7 @@ module.exports = class Simulator extends CocoClass
@simulateAnotherTaskAfterDelay()
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
console.log "Assigning world and level"
@world = @levelLoader.world
@level = @levelLoader.level
@levelLoader.destroy()
@ -81,18 +99,45 @@ module.exports = class Simulator extends CocoClass
setupGod: ->
@god.level = @level.serialize @supermodel
@god.worldClassMap = @world.classMap
@god.setWorldClassMap @world.classMap
@setupGoalManager()
@setupGodSpells()
setupGoalManager: ->
@god.goalManager = new GoalManager @world, @level.get 'goals'
@god.setGoalManager new GoalManager(@world, @level.get 'goals')
commenceSimulationAndSetupCallback: ->
@god.createWorld()
@god.createWorld @generateSpellsObject()
Backbone.Mediator.subscribeOnce 'god:infinite-loop', @onInfiniteLoop, @
Backbone.Mediator.subscribeOnce 'god:new-world-created', @processResults, @
#Search for leaks, headless-client only.
if @options.headlessClient and @options.leakTest and not @memwatch?
leakcount = 0
maxleakcount = 0
console.log "Setting leak callbacks."
@memwatch = require 'memwatch'
@memwatch.on 'leak', (info) =>
console.warn "LEAK!!\n" + JSON.stringify(info)
unless @hd?
if (leakcount++ is maxleakcount)
@hd = new @memwatch.HeapDiff()
@memwatch.on 'stats', (stats) =>
console.warn "stats callback: " + stats
diff = @hd.end()
console.warn "HeapDiff:\n" + JSON.stringify(diff)
if @options.exitOnLeak
console.warn "Exiting because of Leak."
process.exit()
@hd = new @memwatch.HeapDiff()
onInfiniteLoop: ->
console.warn "Skipping infinitely looping game."
@trigger 'statusUpdate', "Infinite loop detected; grabbing a new game in #{@retryDelayInSeconds} seconds."
@ -106,6 +151,9 @@ module.exports = class Simulator extends CocoClass
@trigger 'statusUpdate', 'Simulation completed, sending results back to server!'
console.log "Sending result back to server!"
if @options.headlessClient and @options.testing
return @fetchAndSimulateTask()
$.ajax
url: "/queue/scoring"
data: results
@ -117,8 +165,11 @@ module.exports = class Simulator extends CocoClass
handleTaskResultsTransferSuccess: (result) =>
console.log "Task registration result: #{JSON.stringify result}"
@trigger 'statusUpdate', 'Results were successfully sent back to server!'
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
$('#simulated-by-you').text(simulatedBy)
console.log "Simulated by you: " + @simulatedByYou
@simulatedByYou++
unless @options.headlessClient
simulatedBy = parseInt($('#simulated-by-you').text(), 10) + 1
$('#simulated-by-you').text(simulatedBy)
handleTaskResultsTransferError: (error) =>
@trigger 'statusUpdate', 'There was an error sending the results back to the server.'
@ -144,7 +195,6 @@ module.exports = class Simulator extends CocoClass
sessions: []
for session in @task.getSessions()
sessionResult =
sessionID: session.sessionID
submitDate: session.submitDate

View file

@ -168,10 +168,12 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
stop: ->
@imageObject?.stop?()
mark.stop() for name, mark of @marks
@stopped = true
play: ->
@imageObject?.play?()
mark.play() for name, mark of @marks
@stopped = false
update: (frameChanged) ->
# Gets the sprite to reflect what the current state of the thangs and surface are
@ -222,7 +224,8 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
getBobOffset: ->
return 0 unless @thang.bobHeight
@thang.bobHeight * (1 + Math.sin(@age * Math.PI / @thang.bobTime))
return @lastBobOffset if @stopped
return @lastBobOffset = @thang.bobHeight * (1 + Math.sin(@age * Math.PI / @thang.bobTime))
getWorldPosition: ->
p1 = if @possessed then @shadow.pos else @thang.pos
@ -495,6 +498,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
updateEffectMarks: ->
return if _.isEqual @thang.effectNames, @previousEffectNames
return if @stopped
for effect in @thang.effectNames
mark = @addMark effect, @options.floatingLayer, effect
mark.statusEffect = true

View file

@ -221,12 +221,12 @@ module.exports = class SpriteBoss extends CocoClass
onCastSpells: -> @stop()
play: ->
sprite.imageObject.play() for sprite in @spriteArray
sprite.play() for sprite in @spriteArray
@selectionMark?.play()
@targetMark?.play()
stop: ->
sprite.imageObject.stop() for sprite in @spriteArray
sprite.stop() for sprite in @spriteArray
@selectionMark?.stop()
@targetMark?.stop()

View file

@ -204,7 +204,7 @@ module.exports = class GoalManager extends CocoClass
arrays = (prop for prop in whos when prop?.length)
return unless arrays.length
state[progressObjectName] = {}
state[progressObjectName] ?= {}
for array in arrays
for thang in array
if @thangTeams[thang]?
@ -235,7 +235,7 @@ module.exports = class GoalManager extends CocoClass
numNeeded = goal.howMany ? Math.max(1, _.size stateThangs)
else
# saveThangs: by default we would want to save all the Thangs, which means that we would want none of them to be "done"
numNeeded = _.size(stateThangs) - Math.min((goal.howMany ? 1), _.size stateThangs) + 1
numNeeded = _.size(stateThangs) - Math.max((goal.howMany ? 1), _.size stateThangs) + 1
numDone = _.filter(stateThangs).length
console.log "needed", numNeeded, "done", numDone, "of total", _.size(stateThangs), "with how many", goal.howMany, "and stateThangs", stateThangs
return unless numDone >= numNeeded

View file

@ -72,7 +72,7 @@ module.exports = class World
(@runtimeErrors ?= []).push error
(@unhandledRuntimeErrors ?= []).push error
loadFrames: (loadedCallback, errorCallback, loadProgressCallback) ->
loadFrames: (loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading) ->
return if @aborted
unless @thangs.length
console.log "Warning: loadFrames called on empty World (no thangs)."
@ -96,7 +96,11 @@ module.exports = class World
if t2 - @t0 > 1000
console.log(' Loaded', i, 'of', @totalFrames, "(+" + (t2 - @t0).toFixed(0) + "ms)")
@t0 = t2
setTimeout((=> @loadFrames(loadedCallback, errorCallback, loadProgressCallback)), 0)
continueFn = => @loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading)
if skipDeferredLoading
continueFn()
else
setTimeout(continueFn, 0)
return
@ended = true
system.finish @thangs for system in @systems
@ -336,7 +340,7 @@ module.exports = class World
console.log "Whoa, serializing a lot of WorldScriptNotes here:", o.scriptNotes.length
{serializedWorld: o, transferableObjects: [o.storageBuffer]}
@deserialize: (o, classMap, oldSerializedWorldFrames, worldCreationTime, finishedWorldCallback) ->
@deserialize: (o, classMap, oldSerializedWorldFrames, finishedWorldCallback) ->
# Code hotspot; optimize it
#console.log "Deserializing", o, "length", JSON.stringify(o).length
#console.log JSON.stringify(o)

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "български език", englishDescri
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "български език", englishDescri
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Català", englishDescription: "Catalan", tr
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
multiplayer_hint_label: "Tip:"
multiplayer_hint: " Klikněte na odkaz pro jeho výběr, poté stiskněte ⌘-C nebo Ctrl-C pro kopírování odkazu."
multiplayer_coming_soon: "Další vlastnosti multiplayeru jsou na cestě!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Průvodce"
tome_minion_spells: "Vaše oblíbená kouzla"
tome_read_only_spells: "Kouzla jen pro čtení"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
multiplayer_hint_label: "Tip:"
multiplayer_hint: " Klik på linket for markere alt; tryk derefter ⌘-C eller Ctrl-C tfr at kopiere linket."
multiplayer_coming_soon: "Yderligere flerspillermuligheder er på vej!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Instruktioner"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch (Österreich)", englishDescription:
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch (Schweiz)", englishDescription: "Ge
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
multiplayer_hint_label: "Hinweis:"
multiplayer_hint: " Klick den Link, um alles auszuwählen, dann drück ⌘-C oder Strg-C um den Link zu kopieren."
multiplayer_coming_soon: "Mehr Multiplayerfeatures werden kommen!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Anleitung"
tome_minion_spells: "Die Zaubersprüche Deiner Knechte"
tome_read_only_spells: "Nur-lesen Zauberspüche"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Deutsch", englishDescription: "German", tra
multiplayer_hint_label: "Hinweis:"
multiplayer_hint: " Klick den Link, um alles auszuwählen, dann drück ⌘-C oder Strg-C um den Link zu kopieren."
multiplayer_coming_soon: "Mehr Multiplayerfeatures werden kommen!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Anleitung"
tome_minion_spells: "Die Zaubersprüche Deiner Knechte"
tome_read_only_spells: "Nur-lesen Zauberspüche"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Deutsch", englishDescription: "German", tra
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "ελληνικά", englishDescription: "Gre
multiplayer_hint_label: "Συμβουλή:"
multiplayer_hint: " Κάντε κλικ στο σύνδεσμο για να επιλέξετε όλα, στη συνέχεια, πατήστε την Apple-C ή Ctrl-C για να αντιγράψετε το σύνδεσμο."
multiplayer_coming_soon: "Περισσότερα multiplayer χαρακτιριστηκα προσεχως!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Οδηγός"
tome_minion_spells: "Ξόρκια για τα τσιράκια σας"
tome_read_only_spells: "Ξορκια μονο για αναγνωση"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "ελληνικά", englishDescription: "Gre
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@
multiplayer_hint_label: "Hint:"
multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
multiplayer_coming_soon: "More multiplayer features to come!"
multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guide"
tome_minion_spells: "Your Minions' Spells"
tome_read_only_spells: "Read-Only Spells"
@ -710,4 +711,4 @@
user_names: "User Names"
files: "Files"
top_simulators: "Top Simulators"
source_document: "Source Document"
source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "español (América Latina)", englishDescrip
multiplayer_hint_label: "Consejo:"
multiplayer_hint: " Cliquea el enlace para seleccionar todo, luego presiona ⌘-C o Ctrl-C para copiar el enlace."
multiplayer_coming_soon: "¡Más características de multijugador por venir!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guía"
tome_minion_spells: "Hechizos de tus Secuaces"
tome_read_only_spells: "Hechizos de Sólo Lectura"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "español (América Latina)", englishDescrip
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
multiplayer_hint_label: "Pista:"
multiplayer_hint: " Haz un click en el link para que se seleccione, después utiliza Ctrl-C o ⌘-C para copiar el link."
multiplayer_coming_soon: "¡Más opciones de Multijugador están por venir!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guía"
tome_minion_spells: "Los hechizos de tus súbditos"
tome_read_only_spells: "Hechizos de solo lectura"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "español", englishDescription: "Spanish", t
multiplayer_hint_label: "Consejo:"
multiplayer_hint: " Cliquea el enlace para seleccionar todo, luego presiona ⌘-C o Ctrl-C para copiar el enlace."
multiplayer_coming_soon: "¡Más características de multijugador por venir!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guía"
tome_minion_spells: "Hechizos de tus Secuaces"
tome_read_only_spells: "Hechizos de Sólo Lectura"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "español", englishDescription: "Spanish", t
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
multiplayer_hint_label: "Astuce:"
multiplayer_hint: " Cliquez sur le lien pour tout sélectionner, puis appuyer sur Pomme-C ou Ctrl-C pour copier le lien."
multiplayer_coming_soon: "Plus de fonctionnalités multijoueurs sont à venir"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guide"
tome_minion_spells: "Les sorts de vos soldats"
tome_read_only_spells: "Sorts en lecture-seule"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
multiplayer_hint_label: "Tipp:"
multiplayer_hint: " Kattints a linkre, és Ctrl+C-vel (vagy ⌘+C-vel) másold a vágólapra!"
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Útmutató"
tome_minion_spells: "Egységeid varázslatai"
tome_read_only_spells: "Csak olvasható varázslatok"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
multiplayer_hint_label: "Suggerimento:"
multiplayer_hint: " Clicca il link per selezionare tutto, quindi premi CMD-C o Ctrl-C per copiare il link."
multiplayer_coming_soon: "Ulteriori aggiunte per il multigiocatore in arrivo!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guida"
tome_minion_spells: "Incantesimi dei tuoi seguaci"
tome_read_only_spells: "Incantesimi in sola lettura"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Italiano", englishDescription: "Italian", t
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
multiplayer_hint_label: "ヒント:"
multiplayer_hint: " リンクを選択後、 ⌘-C(MacOS) or Ctrl-C(Windows) でリンクをコピーできます。"
multiplayer_coming_soon: "今後より多くのマルチプレイ機能が追加されます。"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "ガイド"
tome_minion_spells: "操作できるキャラクターの呪文"
tome_read_only_spells: "読込専用の呪文"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
multiplayer_hint_label: "힌트:"
multiplayer_hint: " 모두 선택하려면 링크를 클릭하세요, 그리고 ⌘-C 또는 Ctrl-C 를 눌러서 링크를 복사하세요."
multiplayer_coming_soon: "곧 좀 더 다양한 멀티플레이어 모드가 업데이트 됩니다!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "가이드"
tome_minion_spells: "당신 미니언의' 마법"
tome_read_only_spells: "읽기 전용 마법"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "lietuvių kalba", englishDescription: "Lith
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
multiplayer_hint_label: "Hint:"
multiplayer_hint: " Klikk lenken for å velge alle, så trykker du Apple-C eller Ctrl-C for å kopiere lenken."
multiplayer_coming_soon: "Det kommer flere flerspillsmuligheter!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guide"
tome_minion_spells: "Din Minions' Trylleformularer"
tome_read_only_spells: "Kun-Lesbare Trylleformularer"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
multiplayer_hint_label: "Hint:"
multiplayer_hint: " Klik de link om alles te selecteren, druk dan op Apple-C of Ctrl-C om de link te kopiëren."
multiplayer_coming_soon: "Binnenkort komen er meer Multiplayermogelijkheden!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Handleiding"
tome_minion_spells: "Jouw Minions' Spreuken"
tome_read_only_spells: "Read-Only Spreuken"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Nederlands (België)", englishDescription:
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
multiplayer_hint_label: "Hint:"
multiplayer_hint: " Klik de link om alles te selecteren, druk dan op Apple-C of Ctrl-C om de link te kopiëren."
multiplayer_coming_soon: "Binnenkort komen er meer Multiplayermogelijkheden!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Handleiding"
tome_minion_spells: "Jouw Minions' Spreuken"
tome_read_only_spells: "Read-Only Spreuken"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Nederlands (Nederland)", englishDescription
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -16,7 +16,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
play: "Spelen"
retry: "Probeer opnieuw"
watch: "Volgen"
unwatch: "Ontvolgen"
unwatch: "Ontvolgen"
submit_patch: "Correctie Opsturen"
units:
@ -199,7 +199,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
employers:
want_to_hire_our_players: "Wil je expert CodeCombat spelers aanwerven? "
see_candidates: "Klik om je kandidaten te zien"
see_candidates: "Klik om je kandidaten te zien"
candidates_count_prefix: "Momenteel hebben we "
candidates_count_many: "veel"
candidates_count_suffix: "zeer getalenteerde en ervaren ontwikkelaars die werk zoeken."
@ -246,6 +246,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
multiplayer_hint_label: "Hint:"
multiplayer_hint: " Klik de link om alles te selecteren, druk dan op Apple-C of Ctrl-C om de link te kopiëren."
multiplayer_coming_soon: "Binnenkort komen er meer Multiplayermogelijkheden!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Handleiding"
tome_minion_spells: "Jouw Minions' Spreuken"
tome_read_only_spells: "Read-Only Spreuken"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
user_names: "Gebruikersnamen"
files: "Bestanden"
top_simulators: "Top Simulatoren"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Norwegian Nynorsk", englishDescription: "No
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
multiplayer_hint_label: "Hint:"
multiplayer_hint: " Klikk lenken for å velge alle, så trykker du Apple-C eller Ctrl-C for å kopiere lenken."
multiplayer_coming_soon: "Det kommer flere flerspillsmuligheter!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guide"
tome_minion_spells: "Din Minions' Trylleformularer"
tome_read_only_spells: "Kun-Lesbare Trylleformularer"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
multiplayer_hint_label: "Podpowiedź:"
multiplayer_hint: "Kliknij link by zaznaczyć wszystko, potem wciśnij Cmd-C lub Ctrl-C by skopiować ten link."
multiplayer_coming_soon: "Wkrótce więcej opcji multiplayer"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Przewodnik"
tome_minion_spells: "Czary twojego podopiecznego"
tome_read_only_spells: "Czary tylko do odczytu"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
multiplayer_hint_label: "Dica:"
multiplayer_hint: " Clique no link para selecionar tudo, então dê Ctrl+C ou ⌘+C para copiar o link. "
multiplayer_coming_soon: "Mais novidades no multiplayer estão chegando!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guia"
tome_minion_spells: "Magias dos seus subordinados"
tome_read_only_spells: "Magias não editáveis"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Português europeu", englishDescription: "P
multiplayer_hint_label: "Dica:"
multiplayer_hint: " Carrega no link para seleccionar tudp, depois pressiona ⌘-C ou Ctrl-C para copiar o link."
multiplayer_coming_soon: "Mais funcionalidades de multiplayer brevemente!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guia"
tome_minion_spells: "Feitiços dos teus Minions"
tome_read_only_spells: "Feitiços apenas de leitura"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Português europeu", englishDescription: "P
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "português", englishDescription: "Portugues
multiplayer_hint_label: "Dica:"
multiplayer_hint: " Clique no link para selecionar tudo, então dê Ctrl+C ou ⌘+C para copiar o link. "
multiplayer_coming_soon: "Mais novidades no multiplayer estão chegando!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guia"
tome_minion_spells: "Magias dos seus subordinados"
tome_read_only_spells: "Magias não editáveis"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "português", englishDescription: "Portugues
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
multiplayer_hint_label: "Hint:"
multiplayer_hint: " Apasă pe link pentru a selecta tot, apoi apasă ⌘-C sau Ctrl-C pentru a copia link-ul."
multiplayer_coming_soon: "Mai multe feature-uri multiplayer în curând!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Ghid"
tome_minion_spells: "Vrăjile Minion-ilor tăi"
tome_read_only_spells: "Vrăji Read-Only"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
multiplayer_hint_label: "Подсказка: "
multiplayer_hint: "кликните на ссылку, чтобы выделить её, затем нажмите ⌘-С или Ctrl-C, чтобы скопировать."
multiplayer_coming_soon: "Больше возможностей мультиплеера на подходе!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Руководство"
tome_minion_spells: "Заклинания ваших миньонов"
tome_read_only_spells: "Заклинания только для чтения"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
multiplayer_hint_label: "Мала помоћ"
multiplayer_hint: " Кликни на линк да обележиш све, затим притисни Apple-C или Ctrl-C да копираш линк."
multiplayer_coming_soon: "Стиже још нових карактеристика!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Водич"
tome_minion_spells: "Чини твојих поданика"
tome_read_only_spells: "Чини које се могу само гледати"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
multiplayer_hint_label: "Tips:"
multiplayer_hint: " Klicka på länken för att välja allt, tryck sedan på Cmd-C eller Ctrl-C för att kopiera länken."
multiplayer_coming_soon: "Fler flerspelarlägen kommer!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Guide"
tome_minion_spells: "Dina soldaters förmågor"
tome_read_only_spells: "Skrivskyddade förmågor"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
multiplayer_hint_label: "คำใบ้"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
multiplayer_hint_label: "İpucu:"
multiplayer_hint: " Kopyalamak için önce linke tıklayın, ardından CTRL+C veya ⌘+C kombinasyonuna basın."
multiplayer_coming_soon: "Daha bir çok çoklu oyuncu özelliği eklenecek!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Rehber"
tome_minion_spells: "Minyonlarınızın Büyüleri"
tome_read_only_spells: "Salt Okunur Büyüler"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "українська мова", englishDesc
multiplayer_hint_label: "Підказка:"
multiplayer_hint: "Натисніть на посилання, щоб обрати всіх, та натисніть Apple-C або Ctrl-C, щоб скопіювати посилання."
multiplayer_coming_soon: "Скоро - більше можливостей у мультиплеєрі!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "Посібник"
tome_minion_spells: "Закляття ваших міньонів"
tome_read_only_spells: "Закляття тільки для читання"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "українська мова", englishDesc
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
multiplayer_hint_label: "提示:"
multiplayer_hint: " 点击全选,然后按 Apple-C苹果电脑或 Ctrl-C 复制链接。"
multiplayer_coming_soon: "多人游戏的更多特性!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "指南"
tome_minion_spells: "助手的咒语"
tome_read_only_spells: "只读的咒语"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
multiplayer_hint_label: "提示:"
multiplayer_hint: " 點擊全選,然後按 ⌘-C 或 Ctrl-C 複製連結。"
multiplayer_coming_soon: "請期待更多的多人關卡!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "指南"
tome_minion_spells: "助手的咒語"
tome_read_only_spells: "唯讀的咒語"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "吴语", englishDescription: "Wuu (Simplifi
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -246,6 +246,7 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
multiplayer_hint_label: "提醒:"
multiplayer_hint: " 點牢全選,再捺 Apple-C蘋果電腦要勿 Ctrl-C 複製鏈接。"
multiplayer_coming_soon: "多人遊戲還多特性!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
guide_title: "指南"
tome_minion_spells: "下手個咒語"
tome_read_only_spells: "只讀個咒語"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "吳語", englishDescription: "Wuu (Traditio
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -17,7 +17,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
retry: "重试"
# watch: "Watch"
# unwatch: "Unwatch"
# submit_patch: "Submit Patch"
submit_patch: "提交补丁"
units:
second: ""
@ -36,7 +36,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
nav:
play: ""
# community: "Community"
community: "社区"
editor: "编辑"
blog: "博客"
forum: "论坛"
@ -53,7 +53,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
versions:
save_version_title: "保存新版本"
new_major_version: "最新主要版本"
# cla_prefix: "To save changes, first you must agree to our"
cla_prefix: "要保存更改, 首先你必须要统一我们的"
# cla_url: "CLA"
# cla_suffix: "."
cla_agree: "我同意"
@ -61,7 +61,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
login:
sign_up: "注册"
log_in: "登录"
# logging_in: "Logging In"
logging_in: "登录中..."
log_out: "登出"
recover: "找回账户"
@ -78,7 +78,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
creating: "账户在创新中"
sign_up: "注册"
log_in: "以密码登录"
# social_signup: "Or, you can sign up through Facebook or G+:"
social_signup: "或者, 你可以通过Facebook 或者 G+ 注册:"
home:
slogan: "通过游戏学习Javascript脚本语言"
@ -130,21 +130,21 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
# learn_more: "Learn more about being a Diplomat"
# subscribe_as_diplomat: "Subscribe as a Diplomat"
# wizard_settings:
# title: "Wizard Settings"
# customize_avatar: "Customize Your Avatar"
# active: "Active"
# color: "Color"
# group: "Group"
# clothes: "Clothes"
# trim: "Trim"
# cloud: "Cloud"
# team: "Team"
# spell: "Spell"
# boots: "Boots"
# hue: "Hue"
# saturation: "Saturation"
# lightness: "Lightness"
wizard_settings:
title: "巫师设定"
customize_avatar: "设置你的头像"
active: "启用"
color: "颜色"
group: "类别"
clothes: "衣服"
trim: "条纹"
cloud: ""
team: "队伍"
spell: "魔法球"
boots: "鞋子"
hue: "色彩"
saturation: "饱和度"
lightness: "亮度"
# account_settings:
# title: "Account Settings"
@ -246,6 +246,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
# multiplayer_hint_label: "Hint:"
# multiplayer_hint: " Click the link to select all, then press ⌘-C or Ctrl-C to copy the link."
# multiplayer_coming_soon: "More multiplayer features to come!"
# multiplayer_sign_in_leaderboard: "Sign in or create an account and get your solution on the leaderboard."
# guide_title: "Guide"
# tome_minion_spells: "Your Minions' Spells"
# tome_read_only_spells: "Read-Only Spells"
@ -710,3 +711,4 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
# user_names: "User Names"
# files: "Files"
# top_simulators: "Top Simulators"
# source_document: "Source Document"

View file

@ -1,6 +1,5 @@
storage = require 'lib/storage'
deltasLib = require 'lib/deltas'
auth = require 'lib/auth'
class CocoModel extends Backbone.Model
idAttribute: "_id"
@ -9,6 +8,8 @@ class CocoModel extends Backbone.Model
saveBackups: false
@schema: null
getMe: -> @me or @me = require('lib/auth').me
initialize: ->
super()
if not @constructor.className
@ -96,7 +97,8 @@ class CocoModel extends Backbone.Model
not _.isEqual @attributes, @_revertAttributes
cloneNewMinorVersion: ->
newData = $.extend(null, {}, @attributes)
newData = _.clone @attributes
clone = new @constructor(newData)
clone
@ -136,7 +138,7 @@ class CocoModel extends Backbone.Model
hasReadAccess: (actor) ->
# actor is a User object
actor ?= auth.me
actor ?= @getMe()
return true if actor.isAdmin()
if @get('permissions')?
for permission in @get('permissions')
@ -148,7 +150,7 @@ class CocoModel extends Backbone.Model
hasWriteAccess: (actor) ->
# actor is a User object
actor ?= auth.me
actor ?= @getMe()
return true if actor.isAdmin()
if @get('permissions')?
for permission in @get('permissions')

View file

@ -24,3 +24,15 @@ module.exports = class LevelSession extends CocoModel
code = @get('code')
parts = spellKey.split '/'
code?[parts[0]]?[parts[1]]
readyToRank: ->
return false unless @get('levelID') # If it hasn't been denormalized, then it's not ready.
return false unless c1 = @get('code')
return false unless team = @get('team')
return true unless c2 = @get('submittedCode')
thangSpellArr = (s.split("/") for s in @get('teamSpells')[team])
for item in thangSpellArr
thang = item[0]
spell = item[1]
return true if c1[thang][spell] isnt c2[thang][spell]
false

View file

@ -4,6 +4,7 @@ ThangComponentSchema = require './thang_component'
SpecificArticleSchema = c.object()
c.extendNamedProperties SpecificArticleSchema # name first
SpecificArticleSchema.properties.body = { type: 'string', title: 'Content', description: "The body content of the article, in Markdown.", format: 'markdown' }
SpecificArticleSchema.properties.i18n = {type: "object", format: 'i18n', props: ['name', 'body'], description: "Help translate this article"}
SpecificArticleSchema.displayProperty = 'name'
side = {title: "Side", description: "A side.", type: 'string', 'enum': ['left', 'right', 'top', 'bottom']}

View file

@ -27,10 +27,11 @@ block modal-body-content
if ladderGame
if me.get('anonymous')
p Sign in or create an account and get your solution on the leaderboard!
p(data-i18n="play_level.multiplayer_sign_in_leaderboard") Sign in or create an account and get your solution on the leaderboard.
else if readyToRank
button.btn.btn-success.rank-game-button(data-i18n="play_level.victory_rank_my_game") Rank My Game
else
a#go-to-leaderboard-button.btn.btn-primary(href="/play/ladder/#{levelSlug}#my-matches") Go to the leaderboard!
p You can submit your game to be ranked from the leaderboard page.
a.btn.btn-primary(href="/play/ladder/#{levelSlug}#my-matches", data-i18n="play_level.victory_go_ladder") Return to Ladder
block modal-footer-content
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close

View file

@ -72,7 +72,7 @@ module.exports = class MyMatchesTabView extends CocoView
for team in @teams
team.session = (s for s in @sessions.models when s.get('team') is team.id)[0]
team.readyToRank = @readyToRank(team.session)
team.readyToRank = team.session?.readyToRank()
team.isRanking = team.session?.get('isRanking')
team.matches = (convertMatch(match, team.session.get('submitDate')) for match in team.session?.get('matches') or [])
team.matches.reverse()
@ -84,7 +84,7 @@ module.exports = class MyMatchesTabView extends CocoView
if scoreHistory?.length > 1
team.scoreHistory = scoreHistory
scoreHistory = _.last scoreHistory, 100 # Chart URL needs to be under 2048 characters for GET
team.currentScore = Math.round scoreHistory[scoreHistory.length - 1][1] * 100
team.chartColor = team.primaryColor.replace '#', ''
#times = (s[0] for s in scoreHistory)
@ -108,36 +108,35 @@ module.exports = class MyMatchesTabView extends CocoView
sessionID = button.data('session-id')
session = _.find @sessions.models, {id: sessionID}
rankingState = 'unavailable'
if @readyToRank session
if session.readyToRank()
rankingState = 'rank'
else if session.get 'isRanking'
rankingState = 'ranking'
@setRankingButtonText button, rankingState
@$el.find('.score-chart-wrapper').each (i, el) =>
scoreWrapper = $(el)
team = _.find @teams, name: scoreWrapper.data('team-name')
@generateScoreLineChart(scoreWrapper.attr('id'), team.scoreHistory, team.name)
generateScoreLineChart: (wrapperID, scoreHistory,teamName) =>
margin =
margin =
top: 20
right: 20
bottom: 30
left: 50
width = 450 - margin.left - margin.right
height = 125
x = d3.time.scale().range([0,width])
y = d3.scale.linear().range([height,0])
xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(4).outerTickSize(0)
yAxis = d3.svg.axis().scale(y).orient("left").ticks(4).outerTickSize(0)
line = d3.svg.line().x(((d) -> x(d.date))).y((d) -> y(d.close))
selector = "#" + wrapperID
svg = d3.select(selector).append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
@ -150,12 +149,10 @@ module.exports = class MyMatchesTabView extends CocoView
date: time
close: d[1] * 100
}
x.domain(d3.extent(data, (d) -> d.date))
y.domain(d3.extent(data, (d) -> d.close))
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
@ -172,21 +169,6 @@ module.exports = class MyMatchesTabView extends CocoView
.datum(data)
.attr("class",lineClass)
.attr("d",line)
readyToRank: (session) ->
return false unless session?.get('levelID') # If it hasn't been denormalized, then it's not ready.
return false unless c1 = session.get('code')
return false unless team = session.get('team')
return true unless c2 = session.get('submittedCode')
thangSpellArr = (s.split("/") for s in session.get('teamSpells')[team])
for item in thangSpellArr
thang = item[0]
spell = item[1]
return true if c1[thang][spell] isnt c2[thang][spell]
return false
rankSession: (e) ->
button = $(e.target).closest('.rank-button')
@ -202,7 +184,6 @@ module.exports = class MyMatchesTabView extends CocoView
@setRankingButtonText(button, 'failed')
ajaxData = {session: sessionID, levelID: @level.id, originalLevelID: @level.attributes.original, levelMajorVersion: @level.attributes.version.major}
console.log "Posting game for ranking from My Matches view."
$.ajax '/queue/scoring', {
type: 'POST'
data: ajaxData

View file

@ -38,7 +38,7 @@ module.exports = class SimulateTabView extends CocoView
# Simulations
onSimulateButtonClick: (e) ->
$("#simulate-button").prop "disabled",true
$("#simulate-button").prop "disabled", true
$("#simulate-button").text "Simulating..."
@simulator.fetchAndSimulateTask()

View file

@ -56,7 +56,6 @@ module.exports = class ControlBarView extends View
c.multiplayerEnabled = @session.get('multiplayer')
c.ladderGame = @level.get('type') is 'ladder'
c.spectateGame = @spectateGame
console.log "level type is", @level.get('type')
if @level.get('type') in ['ladder', 'ladder-tutorial']
c.homeLink = '/play/ladder/' + @level.get('slug').replace /\-tutorial$/, ''
else

View file

@ -9,7 +9,7 @@ module.exports = class MultiplayerModal extends View
events:
'click textarea': 'onClickLink'
'change #multiplayer': 'updateLinkSection'
'click .rank-game-button': 'onRankGame'
constructor: (options) ->
super(options)
@ -17,20 +17,20 @@ module.exports = class MultiplayerModal extends View
@level = options.level
@listenTo(@session, 'change:multiplayer', @updateLinkSection)
@playableTeams = options.playableTeams
@ladderGame = options.ladderGame
console.log 'ladder game is', @ladderGame
getRenderData: ->
c = super()
c.joinLink = (document.location.href.replace(/\?.*/, '').replace('#', '') +
'?session=' +
@session.id)
c.multiplayer = @session.get('multiplayer')
c.multiplayer = @session.get 'multiplayer'
c.team = @session.get 'team'
c.levelSlug = @level?.get('slug')
c.levelSlug = @level?.get 'slug'
c.playableTeams = @playableTeams
c.ladderGame = @ladderGame
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
if @level?.get('type') is 'ladder'
c.ladderGame = true
c.readyToRank = @session?.readyToRank()
c
afterRender: ->
@ -50,5 +50,20 @@ module.exports = class MultiplayerModal extends View
multiplayer = Boolean(@$el.find('#multiplayer').prop('checked'))
@session.set('multiplayer', multiplayer)
onRankGame: (e) ->
button = @$el.find('.rank-game-button')
button.text($.i18n.t('play_level.victory_ranking_game', defaultValue: 'Submitting...'))
button.prop 'disabled', true
ajaxData = session: @session.id, levelID: @level.id, originalLevelID: @level.get('original'), levelMajorVersion: @level.get('version').major
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
goToLadder = -> Backbone.Mediator.publish 'router:navigate', route: ladderURL
$.ajax '/queue/scoring',
type: 'POST'
data: ajaxData
success: goToLadder
failure: (response) ->
console.error "Couldn't submit game for ranking:", response
goToLadder()
destroy: ->
super()

View file

@ -65,7 +65,6 @@ module.exports = class VictoryModal extends View
ajaxData = session: @session.id, levelID: @level.id, originalLevelID: @level.get('original'), levelMajorVersion: @level.get('version').major
ladderURL = "/play/ladder/#{@level.get('slug')}#my-matches"
goToLadder = -> Backbone.Mediator.publish 'router:navigate', route: ladderURL
console.log "Posting game for ranking from victory modal."
$.ajax '/queue/scoring',
type: 'POST'
data: ajaxData
@ -82,9 +81,7 @@ module.exports = class VictoryModal extends View
c.levelName = utils.i18n @level.attributes, 'name'
c.level = @level
if c.level.get('type') is 'ladder'
c1 = @session?.get('code')
c2 = @session?.get('submittedCode')
c.readyToRank = @session.get('levelID') and c1 and not _.isEqual(c1, c2)
c.readyToRank = @session.readyToRank()
if me.get 'hourOfCode'
# Show the Hour of Code "I'm Done" tracking pixel after they played for 30 minutes
elapsed = (new Date() - new Date(me.get('dateCreated')))

View file

@ -12,7 +12,7 @@ Surface = require 'lib/surface/Surface'
God = require 'lib/God'
GoalManager = require 'lib/world/GoalManager'
ScriptManager = require 'lib/scripts/ScriptManager'
LevelBus = require('lib/LevelBus')
LevelBus = require 'lib/LevelBus'
LevelLoader = require 'lib/LevelLoader'
LevelSession = require 'models/LevelSession'
Level = require 'models/Level'
@ -112,8 +112,8 @@ module.exports = class PlayLevelView extends View
load: ->
@loadStartTime = new Date()
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team")
@god = new God()
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @levelID, sessionID: @sessionID, opponentSessionID: @getQueryVariable('opponent'), team: @getQueryVariable("team")
getRenderData: ->
c = super()
@ -169,7 +169,7 @@ module.exports = class PlayLevelView extends View
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
@loadOpponentTeam(team)
@god.level = @level.serialize @supermodel
@god.worldClassMap = @world.classMap
@god.setWorldClassMap @world.classMap
@setTeam team
@initSurface()
@initGoalManager()
@ -427,7 +427,7 @@ module.exports = class PlayLevelView extends View
initGoalManager: ->
@goalManager = new GoalManager(@world, @level.get('goals'))
@god.goalManager = @goalManager
@god.setGoalManager @goalManager
initScriptManager: ->
@scriptManager = new ScriptManager({scripts: @world.scripts or [], view:@, session: @session})

View file

@ -8,7 +8,7 @@ World = require 'lib/world/world'
# tools
Surface = require 'lib/surface/Surface'
God = require 'lib/God'
God = require 'lib/God' # 'lib/Buddha'
GoalManager = require 'lib/world/GoalManager'
ScriptManager = require 'lib/scripts/ScriptManager'
LevelLoader = require 'lib/LevelLoader'
@ -156,7 +156,7 @@ module.exports = class SpectateLevelView extends View
team = @world.teamForPlayer(0)
@loadOpponentTeam(team)
@god.level = @level.serialize @supermodel
@god.worldClassMap = @world.classMap
@god.setWorldClassMap @world.classMap
@setTeam team
@initSurface()
@initGoalManager()
@ -387,7 +387,7 @@ module.exports = class SpectateLevelView extends View
initGoalManager: ->
@goalManager = new GoalManager(@world, @level.get('goals'))
@god.goalManager = @goalManager
@god.setGoalManager @goalManager
initScriptManager: ->
if @world.scripts

214
headless_client.coffee Normal file
View file

@ -0,0 +1,214 @@
###
This file will simulate games on node.js by emulating the browser environment.
At some point, most of the code can be merged with Simulator.coffee
###
bowerComponentsPath = "./bower_components/"
headlessClientPath = "./headless_client/"
# SETTINGS
options =
workerCode: require headlessClientPath + 'worker_world'
debug: false # Enable logging of ajax calls mainly
testing: true # Instead of simulating 'real' games, use the same one over and over again. Good for leak hunting.
testFile: require headlessClientPath + 'test.js'
leakTest: false # Install callback that tries to find leaks automatically
exitOnLeak: false # Exit if leak is found. Only useful if leaktest is set to true, obviously.
heapdump: false # Dumps the whole heap after every pass. The heap dumps can then be viewed in Chrome browser.
headlessClient: true
options.heapdump = require('heapdump') if options.heapdump
server = if options.testing then "http://127.0.0.1:3000" else "http://codecombat.com"
# Disabled modules
disable = [
'lib/AudioPlayer'
'locale/locale'
'../locale/locale'
]
# Start of the actual code. Setting up the enivronment to match the environment of the browser
# the path used for the loader. __dirname is module dependent.
path = __dirname
m = require 'module'
request = require 'request'
originalLoader = m._load
unhook = () ->
m._load = originalLoader
hook = () ->
m._load = hookedLoader
JASON = require 'jason'
# Global emulated stuff
GLOBAL.window = GLOBAL
GLOBAL.document = location: pathname: "headless_client"
GLOBAL.console.debug = console.log
GLOBAL.Worker = require('webworker-threads').Worker
Worker::removeEventListener = (what) ->
if what is 'message'
@onmessage = -> #This webworker api has only one event listener at a time.
GLOBAL.tv4 = require('tv4').tv4
GLOBAL.marked = setOptions: ->
GLOBAL.navigator =
# userAgent: "nodejs"
platform: "headless_client"
vendor: "codecombat"
opera: false
store = {}
GLOBAL.localStorage =
getItem: (key) => store[key]
setItem: (key, s) => store[key] = s
removeItem: (key) => delete store[key]
# Hook node.js require. See https://github.com/mfncooper/mockery/blob/master/mockery.js
# The signature of this function *must* match that of Node's Module._load,
# since it will replace that.
# (Why is there no easier way?)
hookedLoader = (request, parent, isMain) ->
if request == 'lib/God'
request = 'lib/Buddha'
if request in disable or ~request.indexOf('templates')
console.log 'Ignored ' + request if options.debug
return class fake
else if '/' in request and not (request[0] is '.') or request is 'application'
request = path + '/app/' + request
else if request is 'underscore'
request = 'lodash'
console.log "loading " + request if options.debug
originalLoader request, parent, isMain
#jQuery wrapped for compatibility purposes. Poorly.
GLOBAL.$ = GLOBAL.jQuery = (input) ->
console.log 'Ignored jQuery: ' + input if options.debug
append: (input)-> exports: ()->
cookies = request.jar()
$.ajax = (options) ->
responded = false
url = options.url
if url.indexOf('http')
url = '/' + url unless url[0] is '/'
url = server + url
data = options.data
#if (typeof data) is 'object'
#console.warn JSON.stringify data
#data = JSON.stringify data
console.log "Requesting: " + JSON.stringify options if options.debug
console.log "URL: " + url if options.debug
request
url: url
jar: cookies
json: options.parse
method: options.type
body: data
, (error, response, body) ->
console.log "HTTP Request:" + JSON.stringify options if options.debug and not error
if responded
console.log "\t↳Already returned before." if options.debug
return
if (error)
console.warn "\t↳Returned: error: #{error}"
options.error(error) if options.error?
else
console.log "\t↳Returned: statusCode #{response.statusCode}: #{if options.parse then JSON.stringify body else body}" if options.debug
options.success(body, response, status: response.statusCode) if options.success?
statusCode = response.statusCode if response?
options.complete(status: statusCode) if options.complete?
responded = true
$.extend = (deep, into, from) ->
copy = _.clone(from, deep);
if into
_.assign into, copy
copy = into
copy
$.isArray = (object) ->
_.isArray object
$.isPlainObject = (object) ->
_.isPlainObject object
do (setupLodash = this) ->
GLOBAL._ = require 'lodash'
_.str = require 'underscore.string'
_.string = _.str
_.mixin _.str.exports()
# load Backbone. Needs hooked loader to reroute underscore to lodash.
hook()
GLOBAL.Backbone = require bowerComponentsPath + 'backbone/backbone'
unhook()
Backbone.$ = $
require bowerComponentsPath + 'validated-backbone-mediator/backbone-mediator'
# Instead of mediator, dummy might be faster yet suffice?
#Mediator = class Mediator
# publish: (id, object) ->
# console.Log "Published #{id}: #{object}"
# @subscribe: () ->
# @unsubscribe: () ->
GLOBAL.Aether = require 'aether'
# Set up new loader.
hook()
login = require './login.coffee' #should contain an object containing they keys 'username' and 'password'
#Login user and start the code.
$.ajax
url: '/auth/login'
type: "POST"
data: login
parse: true
error: (error) -> "Bad Error. Can't connect to server or something. " + error
success: (response) ->
console.log "User: " + response
GLOBAL.window.userObject = response # JSON.parse response
User = require 'models/User'
World = require 'lib/world/world'
LevelLoader = require 'lib/LevelLoader'
GoalManager = require 'lib/world/GoalManager'
SuperModel = require 'models/SuperModel'
log = require 'winston'
CocoClass = require 'lib/CocoClass'
Simulator = require 'lib/simulator/Simulator'
sim = new Simulator options
sim.fetchAndSimulateTask()

87
headless_client/test.js Normal file
View file

@ -0,0 +1,87 @@
module.exports = {
"messageGenerated": 1396792689279,
"sessions": [
{
"sessionID": "533a2c4893b95d9319a58049",
"submitDate": "2014-04-06T06:31:11.806Z",
"team": "humans",
"code": {
"ogre-base": {
"chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// Choose your hero! You can only build one hero.\nvar hero;\n//hero = 'ironjaw'; // A leaping juggernaut hero, type 'brawler'.\nhero = 'yugargen'; // A devious spellcaster hero, type 'shaman'.\nif(hero && !this.builtHero) {\n this.builtHero = this.build(hero);\n return;\n}\n\n// Munchkins are weak melee units with 1.25s build cooldown.\n// Throwers are fragile, deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['munchkin', 'thrower', 'munchkin', 'thrower', 'munchkin', 'thrower'];\nvar type = buildOrder[this.built.length % buildOrder.length];\n//this.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);\n//this.say(\"Move\", {to:{x:20, y:30}});{x: 68, y: 29}{x: 70, y: 30}"
},
"programmable-shaman": {
"chooseAction": "if (this.hero !== undefined) {\n this.hero = this.getNearest(enemy);\n}\n// Shamans are spellcasters with a weak magic attack\n// and three spells: 'shrink', 'grow', and 'poison-cloud'.\n// Shrink: target has 2/3 health, 1.5x speed for 5s.\n// Grow: target has double health, half speed for 5s.\n// Once per match, she can cast poison cloud, which does\n// 5 poison dps for 10s to enemies in a 10m radius.\nvar right = 0;\nif(right === 0){this.move({x: 70, y: 40});\n}\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\nif(this.canCast('shrink', enemy)) \n{\n this.castShrink(enemy);\n}\nelse\n{\n this.castGrow(friend);\n}\n\nvar enemiesinpoisonrange = 0;\nfor (var i = 0; i < enemies.lenght; ++i) {\n var enemi = enemies[i];\n if (this.distance(enemi) <= 10) {\n enemiesinpoisonrange++;\n }\n}\nif (enemiesinpoisonrange >= 7) {\n this.castPoisonCloud(enemy);\n}\n//if (this.distance(ogrebase) > 10) {\n// this.move({x: 70, y: 30});\n//}\n//this.say(\"Defend!\", {targetPos: {x: 45, y: 30}});\n\n//this.say(\"Defend!\", {targetPos: {x: 35, y: 30}});\n\n//this.say(\"Defend!\", {targetPos: {x: 25, y: 30}});\n\n//this.say(\"Attack!\", {to:{x:20, y:30}});\n\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(this.canCast('shrink', enemy)) this.castShrink(enemy);\n//if(this.canCast('grow', friend)) this.castGrow(friend);\n//if(this.canCast('poison-cloud', enemy)) this.castPoisonCloud(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 45, y: 30}});\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});"
},
"programmable-brawler": {
"chooseAction": "// The Brawler is a huge melee hero with mighty mass.\n// this.throw() hurls an enemy behind him.\n// this.jumpTo() leaps to a target within 20m every 10s.\n// this.stomp() knocks everyone away, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('jump')) this.jumpTo(enemy.pos);\n//if(!this.getCooldown('stomp') && this.distance(enemy) < 10) this.stomp();\n//if(!this.getCooldown('throw')) this.throw(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 60, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;{x: 68, y: 29}{x: 70, y: 30}"
},
"programmable-librarian": {
"chooseAction": "var enemies = this.getEnemies();\nif (enemies.length === 0)\n return;\nvar enemy = this.getNearest(enemies);\nvar friends = this.getFriends();\nvar friend = this.getNearest(friends);\nvar archer = this.getFriends(type, \"archer\");\nvar soldier = this.getFriends(type, \"soldier\");\nvar hero = this.getFriends(type, \"hushbaum\");\nvar rand = Math.random();\nvar xmove;\nvar ymove;\nfor (var i = 0; i < enemies.length / 3; i += 1) {\n var e = enemies[i];\n var ehealth = Math.floor(e.health);\n if (this.canCast(\"haste\", friend)) {\n this.say(\"Godspeed \" + friend.id + \"!\");\n this.castHaste(friend);\n }\n if (this.canCast(\"haste\", this)) {\n this.say(\"I am Godspeed!\");\n this.castHaste(this);\n }\n if (this.canCast(\"slow\", e)) {\n this.say(\"Chill Out \" + e.id + \"!\");\n this.castSlow(e);\n }\n if (this.distance(e) < 45) {\n this.attack(e);\n this.say(\"Attacking \" + e.id + \" life is \" + ehealth + \".\");\n }\n if (this.health < this.maxHealth * 0.75) {\n if (this.pos.x > 20) {\n this.move({\n x: this.pos.x - 20,\n y: this.pos.y\n });\n } else {\n this.move({\n x: this.pos.x + 20,\n y: this.pos.y\n });\n }\n }\n if (this.canCast(\"regen\", this)) {\n this.castRegen(this);\n this.say(\"I won't die today bitch!\");\n }\n if (friend.health < friend.maxHealth * 0.5) {\n if (this.canCast(\"regen\", friend)) {\n this.say(\"You won't die today \" + friend.id + \".\");\n this.castRegen(friend);\n }\n }\n}\n;"
},
"programmable-tharin": {
"chooseAction": "// Tharin is a melee fighter with shield, warcry, and terrify skills.\n// this.shield() lets him take one-third damage while defending.\n// this.warcry() gives allies within 10m 30% haste for 5s, every 10s.\n// this.terrify() sends foes within 30m fleeing for 5s, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('warcry')) this.warcry();\n//if(!this.getCooldown('terrify')) this.terrify();\n//this.shield();\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 40, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;"
},
"human-base": {
"chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// CHOOSE YOUR HERO! You can only build one hero.\nvar hero;\n//hero = 'tharin'; // A fierce knight with battlecry abilities.\nhero = 'hushbaum'; // A fiery spellcaster hero.\n\nif(hero && !this.builtHero) {\n this.builtHero = this.build(hero);\n return;\n}\n\n// Soldiers are hard-to-kill, low damage melee units with 2s build cooldown.\n// Archers are fragile but deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['soldier', 'soldier', 'archer', 'archer', 'soldier', 'soldier'];\nvar type = buildOrder[this.built.length % buildOrder.length];\nthis.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);\n\n "
}
},
"teamSpells": {
"ogres": [
"programmable-brawler/chooseAction",
"programmable-shaman/chooseAction",
"ogre-base/chooseAction"
],
"humans": [
"programmable-librarian/chooseAction",
"programmable-tharin/chooseAction",
"human-base/chooseAction"
]
},
"levelID": "dungeon-arena",
"creator": "5338c38c4811eff221de2347",
"creatorName": "iC0DE"
},
{
"sessionID": "532a777c2042708b711a6c29",
"submitDate": "2014-03-20T05:45:54.691Z",
"team": "ogres",
"code": {
"ogre-base": {
"chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// Choose your hero! You can only build one hero.\nvar hero;\n//hero = 'ironjaw'; // A leaping juggernaut hero, type 'brawler'.\nhero = 'yugargen'; // A devious spellcaster hero, type 'shaman'.\nif(hero && !this.builtHero) {\n this.builtHero = this.build(hero);\n return;\n}\n\n// Munchkins are weak melee units with 1.25s build cooldown.\n// Throwers are fragile, deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['munchkin', 'munchkin', 'munchkin', 'thrower'];\nvar type = buildOrder[this.built.length % buildOrder.length];\n//this.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);"
},
"programmable-shaman": {
"chooseAction": "// Shamans are spellcasters with a weak magic attack\n// and three spells: 'shrink', 'grow', and 'poison-cloud'.\n// Shrink: target has 2/3 health, 1.5x speed for 5s.\n// Grow: target has double health, half speed for 5s.\n// Once per match, she can cast poison cloud, which does\n// 5 poison dps for 10s to enemies in a 10m radius.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) {\n return; // Chill if all enemies are dead.\n}\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\nif (enemies.length > 5) {\n if(this.canCast('poison-cloud', enemy)) {\n this.castPoisonCloud(enemy);\n return;\n }\n}\n\nif (friends.length > 4) {\n this.attack(enemy); \n}\nfor (var i = 0; i < friends.length; ++i) {\n if (friends[i].health < 0) {\n continue;\n }\n if(friends[i].type == \"thrower\" && this.canCast('shrink', friends[i])) {\n this.castShrink(friends[i]);\n return;\n } \n if(friends[i].type == \"munchkin\" && this.canCast('grow', friends[i])) {\n this.castGrow(friends[i]);\n return;\n } \n}\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(this.canCast('shrink', enemy)) this.castShrink(enemy);\n//if(this.canCast('grow', friend)) this.castGrow(friend);\n//if(this.canCast('poison-cloud', enemy)) this.castPoisonCloud(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 60, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});"
},
"programmable-brawler": {
"chooseAction": "// The Brawler is a huge melee hero with mighty mass.\n// this.throw() hurls an enemy behind him.\n// this.jumpTo() leaps to a target within 20m every 10s.\n// this.stomp() knocks everyone away, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('jump')) this.jumpTo(enemy.pos);\n//if(!this.getCooldown('stomp') && this.distance(enemy) < 10) this.stomp();\n//if(!this.getCooldown('throw')) this.throw(enemy);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 60, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;"
},
"human-base": {
"chooseAction": "// This is the code for your base. Decide which unit to build each frame.\n// Units you build will go into the this.built array.\n// Destroy the enemy base within 60 seconds!\n// Check out the Guide at the top for more info.\n\n// CHOOSE YOUR HERO! You can only build one hero.\nvar hero;\nhero = 'tharin'; // A fierce knight with battlecry abilities.\n//hero = 'hushbaum'; // A fiery spellcaster hero.\n\nif(hero && !this.builtHero) {\n this.builtHero = this.build(hero);\n return;\n}\n\n// Soldiers are hard-to-kill, low damage melee units with 2s build cooldown.\n// Archers are fragile but deadly ranged units with 2.5s build cooldown.\nvar buildOrder = ['archer', 'archer', 'soldier', 'archer', 'soldier'];\nvar type = buildOrder[this.built.length % buildOrder.length];\n//this.say('Unit #' + this.built.length + ' will be a ' + type);\nthis.build(type);"
},
"programmable-tharin": {
"chooseAction": "this.findTypeInRange = function(units, type) {\n for (var i = 0; i < units.length; ++i) {\n var unit = units[i];\n if (unit.type === type && this.distance(unit) < 20)\n return unit;\n }\n return null;\n};\n\nthis.findType = function(units, type) {\n for (var i = 0; i < units.length; ++i) {\n var unit = units[i];\n if (unit.type === type)\n return unit;\n }\n return null;\n};\n\nthis.findHeroInRange = function(units, range) {\n for (var i = 0; i < units.length; ++i) {\n var unit = units[i];\n if ((unit.type === 'shaman' || unit.type === 'brawler') && this.distance(unit) < range)\n return unit;\n }\n return null;\n};\n\n// Tharin is a melee fighter with shield, warcry, and terrify skills.\n// this.shield() lets him take one-third damage while defending.\n// this.warcry() gives allies within 10m 30% haste for 5s, every 10s.\n// this.terrify() sends foes within 30m fleeing for 5s, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\n\n//Enemies\nvar enemyBase = this.findType(enemies, 'base');\nvar brawler = this.findTypeInRange(enemies, 'brawler');\nvar shaman = this.findTypeInRange(enemies, 'shaman');\n\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(!this.getCooldown('warcry')) this.warcry();\n//if(!this.getCooldown('terrify')) this.terrify();\n//this.shield();\n\nif((brawler || shaman) && !this.attackTime)\n{\n this.attackTime = true;\n if(brawler)\n this.say(\"Attack!\", {target: brawler});\n else if(shaman)\n this.say(\"Attack!\", {target: shaman});\n}\nelse if(this.health < 15 && this.getCooldown('terrify'))\n{\n this.terrify();\n}\nelse if(this.findHeroInRange(enemies, 30) && this.getCooldown('terrify'))\n{\n this.terrify();\n}\nelse if(this.health < 25)\n{\n this.shield();\n}\nelse if(brawler && this.distance(brawler) <=10)\n{\n this.attack(brawler);\n}\nelse\n{\n this.attack(enemy);\n}\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 40, y: 40});\n\n// You can store state on this across frames:\n//this.lastHealth = this.health;"
},
"programmable-librarian": {
"chooseAction": "// The Librarian is a spellcaster with a fireball attack\n// plus three useful spells: 'slow', 'regen', and 'haste'.\n// Slow makes a target move and attack at half speed for 5s.\n// Regen makes a target heal 10 hp/s for 10s.\n// Haste speeds up a target by 4x for 5s, once per match.\n\nvar friends = this.getFriends();\nvar enemies = this.getEnemies();\nif (enemies.length === 0) return; // Chill if all enemies are dead.\nvar enemy = this.getNearest(enemies);\nvar friend = this.getNearest(friends);\n\n// Which one do you do at any given time? Only the last called action happens.\n//if(this.canCast('slow', enemy)) this.castSlow(enemy);\n//if(this.canCast('regen', friend)) this.castRegen(friend);\n//if(this.canCast('haste', friend)) this.castHaste(friend);\n//this.attack(enemy);\n\n// You can also command your troops with this.say():\n//this.say(\"Defend!\", {targetPos: {x: 30, y: 30}}));\n//this.say(\"Attack!\", {target: enemy});\n//this.say(\"Move!\", {targetPos: {x: 50, y: 40});"
}
},
"teamSpells": {
"ogres": [
"programmable-brawler/chooseAction",
"programmable-shaman/chooseAction",
"ogre-base/chooseAction"
],
"humans": [
"programmable-librarian/chooseAction",
"programmable-tharin/chooseAction",
"human-base/chooseAction"
]
},
"levelID": "dungeon-arena",
"creator": "53291a80b112e7240f324667",
"creatorName": "Imbal Oceanrage"
}
],
"taskID": "53415d71942d00aa43dbf3e9",
"receiptHandle": "cd50e44db7dbd4cc0bcce047aa822ba2fe3556cf"
}

View file

@ -0,0 +1,209 @@
# function to use inside a webworker.
# This function needs to run inside an environment that has a 'self'.
# This specific worker is targeted towards the node.js headless_client environment.
JASON = require 'jason'
fs = require 'fs'
betterConsole = () ->
self.logLimit = 200;
self.logsLogged = 0;
self.transferableSupported = () -> true
self.console = log: ->
if self.logsLogged++ is self.logLimit
self.postMessage
type: "console-log"
args: ["Log limit " + self.logLimit + " reached; shutting up."]
id: self.workerID
else if self.logsLogged < self.logLimit
args = [].slice.call(arguments)
i = 0
while i < args.length
args[i] = args[i].toString() if args[i].constructor.className is "Thang" or args[i].isComponent if args[i] and args[i].constructor
++i
try
self.postMessage
type: "console-log"
args: args
id: self.workerID
catch error
self.postMessage
type: "console-log"
args: [
"Could not post log: " + args
error.toString()
error.stack
error.stackTrace
]
id: self.workerID
# so that we don't crash when debugging statements happen
self.console.error = self.console.info = self.console.log
GLOBAL.console = console = self.console
self.console
work = () ->
console.log "starting..."
console.log = ->
World = self.require('lib/world/world');
GoalManager = self.require('lib/world/GoalManager');
self.cleanUp = ->
self.world = null
self.goalManager = null
self.postedErrors = {}
self.t0 = null
self.logsLogged = 0
self.runWorld = (args) ->
console.log "Running world inside worker."
self.postedErrors = {}
self.t0 = new Date()
self.postedErrors = false
self.logsLogged = 0
try
self.world = new World(args.worldName, args.userCodeMap)
self.world.loadFromLevel args.level, true if args.level
self.goalManager = new GoalManager(self.world)
self.goalManager.setGoals args.goals
self.goalManager.setCode args.userCodeMap
self.goalManager.worldGenerationWillBegin()
self.world.setGoalManager self.goalManager
catch error
console.log "There has been an error inside the worker."
self.onWorldError error
return
Math.random = self.world.rand.randf # so user code is predictable
console.log "Loading frames."
self.postMessage type: "start-load-frames"
self.world.loadFrames self.onWorldLoaded, self.onWorldError, self.onWorldLoadProgress, true
self.onWorldLoaded = onWorldLoaded = ->
self.postMessage type: "end-load-frames"
self.goalManager.worldGenerationEnded()
t1 = new Date()
diff = t1 - self.t0
transferableSupported = self.transferableSupported()
try
serialized = serializedWorld: undefined # self.world.serialize()
transferableSupported = false
catch error
console.log "World serialization error:", error.toString() + "\n" + error.stack or error.stackTrace
t2 = new Date()
# console.log("About to transfer", serialized.serializedWorld.trackedPropertiesPerThangValues, serialized.transferableObjects);
try
if transferableSupported
self.postMessage
type: "new-world"
serialized: serialized.serializedWorld
goalStates: self.goalManager.getGoalStates()
, serialized.transferableObjects
else
self.postMessage
type: "new-world"
serialized: serialized.serializedWorld
goalStates: self.goalManager.getGoalStates()
catch error
console.log "World delivery error:", error.toString() + "\n" + error.stack or error.stackTrace
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.cleanUp()
self.onWorldError = onWorldError = (error) ->
self.postMessage type: "end-load-frames"
if error instanceof Aether.problems.UserCodeProblem
#console.log "Aether userCodeProblem occured."
unless self.postedErrors[error.key]
problem = error.serialize()
self.postMessage
type: "user-code-problem"
problem: problem
self.postedErrors[error.key] = problem
else
console.log "Non-UserCodeError:", error.toString() + "\n" + error.stack or error.stackTrace
self.cleanUp()
self.onWorldLoadProgress = onWorldLoadProgress = (progress) ->
#console.log "Worker onWorldLoadProgress"
self.postMessage
type: "world-load-progress-changed"
progress: progress
self.abort = abort = ->
#console.log "Abort called for worker."
if self.world and self.world.name
#console.log "About to abort:", self.world.name, typeof self.world.abort
self.world.abort() if typeof self.world isnt "undefined"
self.world = null
self.postMessage type: "abort"
self.cleanUp()
self.reportIn = reportIn = ->
console.log "Reporting in."
self.postMessage type: "reportIn"
self.addEventListener "message", (event) ->
#console.log JSON.stringify event
self[event.data.func] event.data.args
self.postMessage type: "worker-initialized"
world = fs.readFileSync "./public/javascripts/world.js", 'utf8'
#window.BOX2D_ENABLED = true;
newConsole = "newConsole = #{}JASON.stringify newConsole}()";
ret = """
GLOBAL = root = window = self;
GLOBAL.window = window;
self.workerID = "Worker";
console = #{JASON.stringify betterConsole}();
try {
// the world javascript file
#{world};
// Don't let user generated code access stuff from our file system!
self.importScripts = importScripts = null;
self.native_fs_ = native_fs_ = null;
// the actual function
#{JASON.stringify work}();
}catch (error) {
self.postMessage({"type": "console-log", args: ["An unhandled error occured: ", error.toString(), error.stack], id: -1});
}
"""
#console = #{JASON.stringify createConsole}();
#
# console.error = console.info = console.log;
#self.console = console;
#GLOBAL.console = console;
module.exports = new Function(ret)

View file

@ -60,9 +60,13 @@
"gridfs-stream": "0.4.x",
"stream-buffers": "0.2.x",
"sendwithus": "2.0.x",
"aws-sdk":"~2.0.0",
"bayesian-battle":"0.0.x",
"redis": ""
"aws-sdk": "~2.0.0",
"bayesian-battle": "0.0.x",
"redis": "",
"webworker-threads": "~0.4.11",
"node-gyp": "~0.13.0",
"aether": "~0.1.18",
"JASON": "~0.1.3"
},
"devDependencies": {
"jade": "0.33.x",