Merge branch 'master' into production

This commit is contained in:
Nick Winter 2014-05-09 15:09:21 -07:00
commit 9e4fdf6d16
116 changed files with 1761 additions and 182 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

View file

@ -14,8 +14,8 @@ if (!Function.prototype.bind) {
throw new TypeError("Function.prototype.bind (Shim) - target is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis
@ -83,13 +83,277 @@ self.transferableSupported = function transferableSupported() {
var World = self.require('lib/world/world');
var GoalManager = self.require('lib/world/GoalManager');
Aether.addGlobal('Vector', require('lib/world/vector'));
Aether.addGlobal('_', _);
var serializedClasses = {
"Thang": self.require('lib/world/thang'),
"Vector": self.require('lib/world/vector'),
"Rectangle": self.require('lib/world/rectangle')
};
self.currentUserCodeMapCopy = "";
self.currentDebugWorldFrame = 0;
self.stringifyValue = function(value, depth) {
var brackets, i, isArray, isObject, key, prefix, s, sep, size, v, values, _i, _j, _len, _len1, _ref, _ref1, _ref2, _ref3;
if (!value || _.isString(value)) {
return value;
}
if (_.isFunction(value)) {
if (depth === 2) {
return void 0;
} else {
return "<Function>";
}
}
if (value === this.thang && depth) {
return "<this " + value.id + ">";
}
if (depth === 2) {
if (((_ref = value.constructor) != null ? _ref.className : void 0) === "Thang") {
value = "<" + (value.type || value.spriteName) + " - " + value.id + ", " + (value.pos ? value.pos.toString() : 'non-physical') + ">";
} else {
value = value.toString();
}
return value;
}
isArray = _.isArray(value);
isObject = _.isObject(value);
if (!(isArray || isObject)) {
return value.toString();
}
brackets = isArray ? ["[", "]"] : ["{", "}"];
size = _.size(value);
if (!size) {
return brackets.join("");
}
values = [];
if (isArray) {
for (_i = 0, _len = value.length; _i < _len; _i++) {
v = value[_i];
s = this.stringifyValue(v, depth + 1);
if (s !== void 0) {
values.push("" + s);
}
}
} else {
_ref2 = (_ref1 = value.apiProperties) != null ? _ref1 : _.keys(value);
for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) {
key = _ref2[_j];
if (key[0] === "_") continue;
s = this.stringifyValue(value[key], depth + 1);
if (s !== void 0) {
values.push(key + ": " + s);
}
}
}
sep = '\n' + ((function() {
var _k, _results;
_results = [];
for (i = _k = 0; 0 <= depth ? _k < depth : _k > depth; i = 0 <= depth ? ++_k : --_k) {
_results.push(" ");
}
return _results;
})()).join('');
prefix = (_ref3 = value.constructor) != null ? _ref3.className : void 0;
if (isArray) {
if (prefix == null) {
prefix = "Array";
}
}
if (isObject) {
if (prefix == null) {
prefix = "Object";
}
}
prefix = prefix ? prefix + " " : "";
return "" + prefix + brackets[0] + sep + " " + (values.join(sep + ' ')) + sep + brackets[1];
};
var cache = {};
self.invalidateCache = function () {
cache = {};
};
self.retrieveValueFromCache = function (thangID, spellID, variableChain, frame) {
var frameCache, thangCache, spellCache;
if ((frameCache = cache[frame]) && (thangCache = frameCache[thangID]) && (spellCache = thangCache[spellID]))
return spellCache[variableChain.join()];
return undefined;
};
self.updateCache = function (thangID, spellID, variableChain, frame, value) {
var key, keys, currentObject;
keys = [frame,thangID, spellID, variableChain.join()];
currentObject = cache;
for (var i = 0, len = keys.length - 1; i < len; i++)
{
key = keys[i];
if (!(key in currentObject))
currentObject[key] = {};
currentObject = currentObject[key];
}
currentObject[keys[keys.length - 1]] = value;
};
self.retrieveValueFromFrame = function retrieveValueFromFrame(args) {
var cacheValue;
if (args.frame === self.currentDebugWorldFrame && (cacheValue = self.retrieveValueFromCache(args.currentThangID, args.currentSpellID, args.variableChain, args.frame)))
return self.postMessage({type: 'debug-value-return', serialized: {"key": args.variableChain.join("."), "value": cacheValue}});
var retrieveProperty = function retrieveProperty(currentThangID, currentSpellID, variableChain)
{
var prop;
var value;
var keys = [];
for (var i = 0, len = variableChain.length; i < len; i++) {
prop = variableChain[i];
if (prop === "this")
{
value = self.debugWorld.thangMap[currentThangID];
}
else if (i === 0)
{
try
{
var flowStates = self.debugWorld.userCodeMap[currentThangID][currentSpellID].flow.states;
//we have to go to the second last flowState as we run the world for one additional frame
//to collect the flow
value = _.last(flowStates[flowStates.length - 1].statements).variables[prop];
}
catch (e)
{
value = undefined;
}
}
else
{
value = value[prop];
}
keys.push(prop);
if (!value) break;
var classOfValue;
if (classOfValue = serializedClasses[value.CN])
{
if (value.CN === "Thang")
{
var thang = self.debugWorld.thangMap[value.id];
value = thang || "<Thang " + value.id + " (non-existent)>"
}
else
{
value = classOfValue.deserializeFromAether(value);
}
}
}
var serializedProperty = {
"key": keys.join("."),
"value": self.stringifyValue(value,0)
};
self.updateCache(currentThangID,currentSpellID,variableChain, args.frame, serializedProperty.value);
self.postMessage({type: 'debug-value-return', serialized: serializedProperty});
};
self.enableFlowOnThangSpell(args.currentThangID, args.currentSpellID, args.userCodeMap);
self.setupDebugWorldToRunUntilFrame(args);
self.debugWorld.loadFrames(
retrieveProperty.bind({},args.currentThangID, args.currentSpellID, args.variableChain),
self.onDebugWorldError,
self.onDebugWorldProgress,
false,
args.frame
);
};
self.enableFlowOnThangSpell = function (thangID, spellID, userCodeMap) {
try {
if (userCodeMap[thangID][spellID].originalOptions.includeFlow === true &&
userCodeMap[thangID][spellID].originalOptions.noSerializationInFlow === true)
return;
else
{
userCodeMap[thangID][spellID].originalOptions.includeFlow = true;
userCodeMap[thangID][spellID].originalOptions.noSerializationInFlow = true;
var temporaryAether = Aether.deserialize(userCodeMap[thangID][spellID]);
temporaryAether.transpile(temporaryAether.raw);
userCodeMap[thangID][spellID] = temporaryAether.serialize();
}
}
catch (e) {
console.log("there was an error enabling flow on thang spell:" + e)
}
};
self.setupDebugWorldToRunUntilFrame = function (args) {
self.debugPostedErrors = {};
self.debugt0 = new Date();
self.debugPostedErrors = false;
self.logsLogged = 0;
var stringifiedUserCodeMap = JSON.stringify(args.userCodeMap);
var userCodeMapHasChanged = ! _.isEqual(self.currentUserCodeMapCopy, stringifiedUserCodeMap);
self.currentUserCodeMapCopy = stringifiedUserCodeMap;
if (args.frame != self.currentDebugWorldFrame) self.invalidateCache();
if (!self.debugWorld || userCodeMapHasChanged || args.frame < self.currentDebugWorldFrame) {
try {
self.debugWorld = new World(args.worldName, args.userCodeMap);
if (args.level)
self.debugWorld.loadFromLevel(args.level, true);
self.debugGoalManager = new GoalManager(self.debugWorld);
self.debugGoalManager.setGoals(args.goals);
self.debugGoalManager.setCode(args.userCodeMap);
self.debugGoalManager.worldGenerationWillBegin();
self.debugWorld.setGoalManager(self.debugGoalManager);
}
catch (error) {
self.onDebugWorldError(error);
return;
}
Math.random = self.debugWorld.rand.randf; // so user code is predictable
}
self.debugWorld.totalFrames = args.frame; //hack to work around error checking
self.currentDebugWorldFrame = args.frame;
};
self.onDebugWorldLoaded = function onDebugWorldLoaded() {
console.log("World loaded!");
};
self.onDebugWorldError = function onDebugWorldError(error) {
if(!error.isUserCodeProblem) {
console.log("Debug Non-UserCodeError:", error.toString() + "\n" + error.stack || error.stackTrace);
}
return true;
};
self.onDebugWorldProgress = function onDebugWorldProgress(progress) {
self.postMessage({type: 'debug-world-load-progress-changed', progress: progress});
};
self.debugAbort = function () {
if(self.debugWorld && self.debugWorld.name) {
console.log("About to abort:", self.debugWorld.name, typeof self.debugWorld.abort);
if(typeof self.debugWorld !== "undefined")
self.debugWorld.abort();
self.debugWorld = null;
}
self.postMessage({type: 'debugAbort'});
};
self.runWorld = function runWorld(args) {
self.postedErrors = {};
self.t0 = new Date();
self.firstWorld = args.firstWorld;
self.postedErrors = false;
self.logsLogged = 0;
try {
self.world = new World(args.worldName, args.userCodeMap);
if(args.level)
@ -136,11 +400,11 @@ self.onWorldLoaded = function onWorldLoaded() {
};
self.onWorldError = function onWorldError(error) {
if(error instanceof Aether.problems.UserCodeProblem) {
if(!self.postedErrors[error.key]) {
var problem = error.serialize();
self.postMessage({type: 'user-code-problem', problem: problem});
self.postedErrors[error.key] = problem;
if(error.isUserCodeProblem) {
var errorKey = error.userInfo.key;
if(!errorKey || !self.postedErrors[errorKey]) {
self.postMessage({type: 'user-code-problem', problem: error});
self.postedErrors[errorKey] = error;
}
}
else {

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,23 @@ 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, @
@retriveValueFromFrame = _.throttle @retrieveValueFromFrame, 1000
Backbone.Mediator.subscribe 'tome:spell-debug-value-request', @retrieveValueFromFrame, @
@fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false
@fillWorkerPool()
#TODO: have this as a constructor option
@debugWorker = @createDebugWorker()
@currentUserCodeMap = {}
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 +51,40 @@ 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) =>
createDebugWorker: ->
worker = new Worker '/javascripts/workers/worker_world.js'
worker.creationTime = new Date()
worker.addEventListener 'message', @onDebugWorkerMessage
worker
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
onDebugWorkerMessage: (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
switch event.data.type
when "worker-initialized"
worker.initialized = true
when 'new-debug-world'
console.log "New Debug world!"
when 'console-log'
console.log "|" + @id + "'s debugger|", event.data.args...
when 'debug-value-return'
Backbone.Mediator.publish 'god:debug-value-return', event.data.serialized
getAngel: ->
freeAngel = null
@ -83,10 +113,10 @@ module.exports = class God
angelUserCodeProblem: (angel, problem) ->
return if @dead
#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]
#console.log "UserCodeProblem:", '"' + problem.message + '"', "for", problem.userInfo.thangID, "-", problem.userInfo.methodName, 'at', problem.range?[0]
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,26 +131,59 @@ 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()
}}
retrieveValueFromFrame: (args) ->
if not args.thangID or not args.spellID or not args.variableChain then return
args.frame ?= @world.age / @world.dt
@debugWorker.postMessage
func: 'retrieveValueFromFrame'
args:
worldName: @level.name
userCodeMap: @currentUserCodeMap
level: @level
goals: @goalManager?.getGoals()
frame: args.frame
currentThangID: args.thangID
currentSpellID: args.spellID
variableChain: args.variableChain
#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
finishBeholdingWorld: (newWorld) =>
newWorld.findFirstChangedFrame @world
@world = newWorld
@currentUserCodeMap = @filterUserCodeMapWhenFromWorld @world.userCodeMap
errorCount = (t for t in @world.thangs when t.errorsOut).length
Backbone.Mediator.publish('god:new-world-created', world: @world, firstWorld: @firstWorld, errorCount: errorCount, goalStates: @latestGoalStates, team: me.team)
for scriptNote in @world.scriptNotes
@ -131,6 +194,23 @@ module.exports = class God
unless _.find @angels, 'busy'
@spells = null # Don't hold onto old spells; memory leaks
filterUserCodeMapWhenFromWorld: (worldUserCodeMap) ->
newUserCodeMap = {}
for thangName, thang of worldUserCodeMap
newUserCodeMap[thangName] = {}
for spellName,aether of thang
shallowFilteredObject = _.pick aether, ['raw','pure','originalOptions']
newUserCodeMap[thangName][spellName] = _.cloneDeep shallowFilteredObject
newUserCodeMap[thangName][spellName] = _.defaults newUserCodeMap[thangName][spellName],
flow: {}
metrics: {}
problems:
errors: []
infos: []
warnings: []
style: {}
newUserCodeMap
getUserCodeMap: ->
userCodeMap = {}
for spellKey, spell of @spells
@ -144,6 +224,10 @@ module.exports = class God
@dead = true
Backbone.Mediator.unsubscribe('tome:cast-spells', @onTomeCast, @)
@goalManager?.destroy()
@debugWorker?.terminate()
@debugWorker?.removeEventListener 'message', @onDebugWorkerMessage
@debugWorker ?= null
@currentUserCodeMap = null
@goalManager = null
@fillWorkerPool = null
@simulateWorld = null
@ -171,7 +255,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 +339,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 +355,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

@ -59,13 +59,13 @@ module.exports = class LevelLoader extends CocoClass
url = "/db/level/#{@levelID}/session"
url += "?team=#{@team}" if @team
@session = new LevelSession().setURL url
@supermodel.loadModel(@session, 'level_session', {cache:false})
session = new LevelSession().setURL url
@session = @supermodel.loadModel(session, 'level_session', {cache:false}).model
@session.once 'sync', -> @url = -> '/db/level.session/' + @id
if @opponentSessionID
@opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
@supermodel.loadModel(@opponentSession, 'opponent_session')
opponentSession = new LevelSession().setURL "/db/level_session/#{@opponentSessionID}"
@opponentSession = @supermodel.loadModel(opponentSession, 'opponent_session').model
# Supermodel (Level) Loading
@ -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

@ -16,7 +16,7 @@ init = ->
module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null) ->
user = new User(userObject)
user.save({}, {
error: (model,jqxhr,options) ->
error: (model,jqxhr,options) ->
error = parseServerError(jqxhr.responseText)
property = error.property if error.property
if jqxhr.status is 409 and property is 'name'
@ -26,12 +26,12 @@ module.exports.createUser = (userObject, failure=backboneFailure, nextURL=null)
genericFailure(jqxhr)
success: -> if nextURL then window.location.href = nextURL else window.location.reload()
})
module.exports.createUserWithoutReload = (userObject, failure=backboneFailure) ->
user = new User(userObject)
user.save({}, {
error: failure
success: ->
success: ->
Backbone.Mediator.publish("created-user-without-reload")
})

View file

@ -2,15 +2,21 @@ 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'
Aether.addGlobal 'Vector', require 'lib/world/vector'
Aether.addGlobal '_', _
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 +25,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 +49,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 +72,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 +81,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 +94,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 +102,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 +154,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 +168,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 +198,6 @@ module.exports = class Simulator extends CocoClass
sessions: []
for session in @task.getSessions()
sessionResult =
sessionID: session.sessionID
submitDate: session.submitDate
@ -242,8 +295,8 @@ module.exports = class Simulator extends CocoClass
functionName: methodName
protectAPI: useProtectAPI
includeFlow: false
requiresThis: true
yieldConditionally: false
globals: ['Vector', '_']
problems:
jshint_W040: {level: "ignore"}
jshint_W030: {level: "ignore"} # aether_NoEffect instead

View file

@ -379,7 +379,9 @@ module.exports = class SpriteParser
argsSource = argsSource.replace(/cjs(.+)\)/, '"createjs$1)"') # turns cjs.Ease.get(0.5)
args = eval "[#{argsSource}]"
if args[0]?.state?[0]?.t?.search?("shape") is 0 and not _.find(localShapes, bn: args[0].state[0].t)
shadowTween = args[0]?.search?('shape') is 0 and not _.find(localShapes, bn: args[0])
shadowTween = shadowTween or args[0]?.state?[0]?.t?.search?("shape") is 0 and not _.find(localShapes, bn: args[0].state[0].t)
if shadowTween
console.log "Skipping tween", name, argsSource, args, "from localShapes", localShapes, "presumably because it's a shadow we skipped."
return
callExpressions.push {n: name, a: args}

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

@ -246,6 +246,7 @@ module.exports = class Mark extends CocoClass
size = @sprite.getAverageDimension()
size += 60 if @name is 'selection'
size += 60 if @name is 'repair'
size *= @sprite.scaleFactor
scale = size / {selection: 128, target: 128, repair: 320, highlight: 160}[@name]
if @sprite?.thang.spriteName.search(/(dungeon|indoor).wall/i) isnt -1
scale *= 2

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

@ -65,6 +65,7 @@ module.exports = Surface = class Surface extends CocoClass
'god:new-world-created': 'onNewWorld'
'tome:cast-spells': 'onCastSpells'
'level-set-letterbox': 'onSetLetterbox'
'application:idle-changed': 'onIdleChanged'
shortcuts:
'ctrl+\\, ⌘+\\': 'onToggleDebug'
@ -304,15 +305,32 @@ module.exports = Surface = class Surface extends CocoClass
@spriteBoss.stop()
@playbackOverScreen.show()
@ended = true
@setPaused true
Backbone.Mediator.publish 'surface:playback-ended'
else if @currentFrame < @world.totalFrames and @ended
@spriteBoss.play()
@playbackOverScreen.hide()
@ended = false
@setPaused false
Backbone.Mediator.publish 'surface:playback-restarted'
@lastFrame = @currentFrame
onIdleChanged: (e) ->
@setPaused e.idle unless @ended
setPaused: (to) ->
# We want to be able to essentially stop rendering the surface if it doesn't need to animate anything.
# If pausing, though, we want to give it enough time to finish any tweens.
performToggle = =>
createjs.Ticker.setFPS if to then 1 else @options.frameRate
@surfacePauseInterval = null
clearTimeout @surfacePauseInterval if @surfacePauseInterval
if to
@surfacePauseInterval = _.delay performToggle, 2000
else
performToggle()
onCastSpells: ->
@casting = true
@wasPlayingWhenCastingBegan = @playing
@ -575,8 +593,6 @@ module.exports = Surface = class Surface extends CocoClass
@paths.parent.removeChild @paths
@paths = null
# Screenshot
screenshot: (scale=0.25, format='image/jpeg', quality=0.8, zoom=2) ->
# Quality doesn't work with image/png, just image/jpeg and image/webp
[w, h] = [@camera.canvasWidth, @camera.canvasHeight]
@ -586,6 +602,5 @@ module.exports = Surface = class Surface extends CocoClass
#console.log "Screenshot with scale", scale, "format", format, "quality", quality, "was", Math.floor(imageData.length / 1024), "kB"
screenshot = document.createElement("img")
screenshot.src = imageData
#$('body').append(screenshot)
@stage.uncache()
imageData

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

@ -29,8 +29,6 @@ module.exports.thangNames = thangNames =
"Phineas"
"Ferb"
"Felix"
"Arthur"
"Galahad"
"Ezra"
"Lucian"
"Augustus"
@ -174,6 +172,7 @@ module.exports.thangNames = thangNames =
"Snortt"
"Kog"
"Ursa"
"Ragtime"
]
"Ogre Munchkin F": [
"Iyert"
@ -184,6 +183,7 @@ module.exports.thangNames = thangNames =
"Dosha"
"Inski"
"Lacos"
"Upfish"
]
"Ogre M": [
"Krogg"
@ -202,6 +202,7 @@ module.exports.thangNames = thangNames =
"Mokrul"
"Polifemo"
"Muthyala"
"Saltporker"
]
"Ogre F": [
"Nareng"
@ -231,6 +232,8 @@ module.exports.thangNames = thangNames =
"Tuguro"
"York"
"Ork'han"
"Roast Beefy"
"Haggar"
]
"Ogre Fangrider": [
"Dreek"
@ -272,6 +275,7 @@ module.exports.thangNames = thangNames =
"Gogg"
"Ghuk"
"Makas"
"Drun"
]
"Ogre Thrower": [
"Kyrgg"
@ -293,10 +297,12 @@ module.exports.thangNames = thangNames =
"Burl": [
"Borlit"
"Burlosh"
"Dorf"
]
"Griffin Rider": [
"Aeoldan"
"Bestarius"
]
"Potion Master": [
"Snake"
@ -305,6 +311,7 @@ module.exports.thangNames = thangNames =
"Arora"
"Curie"
"Clause"
"Vanders"
]
"Librarian": [
"Hushbaum"
@ -318,4 +325,30 @@ module.exports.thangNames = thangNames =
"Ryder"
"Thoron"
"Mirial"
"Neely"
]
"Knight": [
"Tharin"
"Arthur"
"Galahad"
"Mace"
"Drake"
"Duran"
"Almeric"
"Hunfray"
"Hank"
"Jeph"
"Neville"
]
"Captain": [
"Anya"
"Brigette"
"Sarre"
"Katana"
"Lily"
"Isa"
"Dimia"
"Jane"
"Lia"
"Hardcastle"
]

View file

@ -32,7 +32,7 @@ module.exports.CollisionCategory = class CollisionCategory
# "ground_and_air_<team>" units don't hit ground or air units on their team (so missiles don't hit same team)
sameTeam = @superteamIndex and cat.superteamIndex is @superteamIndex
return false if sameTeam and @ground and @air
# actually, "ground_and_air<team>" units don't hit any ground_and_air units (temp missile collision fix)
return false if @ground and @air and cat.ground and cat.air

View file

@ -8,7 +8,6 @@ WorldScriptNote = require './world_script_note'
{now, consolidateThangs, typedArraySupport} = require './world_utils'
Component = require 'lib/world/component'
System = require 'lib/world/system'
PROGRESS_UPDATE_INTERVAL = 200
DESERIALIZATION_INTERVAL = 20
@ -72,14 +71,18 @@ module.exports = class World
(@runtimeErrors ?= []).push error
(@unhandledRuntimeErrors ?= []).push error
loadFrames: (loadedCallback, errorCallback, loadProgressCallback) ->
loadFrames: (loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) ->
return if @aborted
unless @thangs.length
console.log "Warning: loadFrames called on empty World (no thangs)."
t1 = now()
@t0 ?= t1
if loadUntilFrame
frameToLoadUntil = loadUntilFrame + 1
else
frameToLoadUntil = @totalFrames
i = @frames.length
while i < @totalFrames
while i < frameToLoadUntil
try
@getFrame(i)
++i # increment this after we have succeeded in getting the frame, otherwise we'll have to do that frame again
@ -96,10 +99,19 @@ 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 = =>
if loadUntilFrame
@loadFrames(loadedCallback,errorCallback,loadProgressCallback, skipDeferredLoading, loadUntilFrame)
else
@loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading)
if skipDeferredLoading
continueFn()
else
setTimeout(continueFn, 0)
return
@ended = true
system.finish @thangs for system in @systems
unless loadUntilFrame
@ended = true
system.finish @thangs for system in @systems
loadProgressCallback? 1
loadedCallback()
@ -221,7 +233,7 @@ module.exports = class World
@scriptNotes.push scriptNote
return unless @goalManager
@goalManager.submitWorldGenerationEvent(channel, event, @frames.length)
setGoalState: (goalID, status) ->
@goalManager.setGoalState(goalID, status)
@ -336,7 +348,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

@ -712,3 +712,14 @@
files: "Files"
top_simulators: "Top Simulators"
source_document: "Source Document"
document: "Document" # note to diplomats: not a physical document, a document in MongoDB, ie a record in a database
delta:
added: "Added"
modified: "Modified"
deleted: "Deleted"
moved_index: "Moved Index"
text_diff: "Text Diff"
merge_conflict_with: "MERGE CONFLICT WITH"
no_changes: "No Changes"

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
@ -27,7 +28,7 @@ class CocoModel extends Backbone.Model
clone = super()
clone.set($.extend(true, {}, if withChanges then @attributes else @_revertAttributes))
clone
onError: ->
@loading = false
@ -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')
@ -160,6 +162,10 @@ class CocoModel extends Backbone.Model
getDelta: ->
differ = deltasLib.makeJSONDiffer()
differ.diff @_revertAttributes, @attributes
getDeltaWith: (otherModel) ->
differ = deltasLib.makeJSONDiffer()
differ.diff @attributes, otherModel.attributes
applyDelta: (delta) ->
newAttributes = $.extend(true, {}, @attributes)
@ -170,13 +176,17 @@ class CocoModel extends Backbone.Model
delta = @getDelta()
deltasLib.expandDelta(delta, @_revertAttributes, @schema())
getExpandedDeltaWith: (otherModel) ->
delta = @getDeltaWith(otherModel)
deltasLib.expandDelta(delta, @attributes, @schema())
watch: (doWatch=true) ->
$.ajax("#{@urlRoot}/#{@id}/watch", {type:'PUT', data:{on:doWatch}})
@watching = -> doWatch
watching: ->
return me.id in (@get('watchers') or [])
populateI18N: (data, schema, path='') ->
# TODO: Better schema/json walking
sum = 0
@ -185,7 +195,7 @@ class CocoModel extends Backbone.Model
if schema.properties?.i18n and _.isPlainObject(data) and not data.i18n?
data.i18n = {}
sum += 1
if _.isPlainObject data
for key, value of data
numChanged = 0
@ -193,10 +203,10 @@ class CocoModel extends Backbone.Model
if numChanged and not path # should only do this for the root object
@set key, value
sum += numChanged
if schema.items and _.isArray data
sum += @populateI18N(value, schema.items, path+'/'+index) for value, index in data
sum
@getReferencedModel: (data, schema) ->
@ -227,13 +237,13 @@ class CocoModel extends Backbone.Model
model = new Model()
model.url = makeUrlFunc(link)
return model
setURL: (url) ->
makeURLFunc = (u) -> -> u
@url = makeURLFunc(url)
@
getURL: ->
return if _.isString @url then @url else @url()
module.exports = CocoModel

View file

@ -31,8 +31,9 @@ module.exports = class SuperModel extends Backbone.Model
else
@registerModel(model)
console.debug 'Registering model', model.getURL()
return @addModelResource(model, name, fetchOptions, value).load()
res = @addModelResource(model, name, fetchOptions, value)
if model.loaded then res.markLoaded() else res.load()
return res
loadCollection: (collection, name, fetchOptions, value=1) ->
url = collection.getURL()
@ -52,7 +53,9 @@ module.exports = class SuperModel extends Backbone.Model
@listenToOnce collection, 'sync', (c) ->
console.debug 'Registering collection', url
@registerCollection c
return @addModelResource(collection, name, fetchOptions, value).load()
res = @addModelResource(collection, name, fetchOptions, value)
res.load() if not (res.isLoading or res.isLoaded)
return res
# replace or overwrite
shouldSaveBackups: (model) -> false

View file

@ -5,7 +5,10 @@
#component-patches
padding: 0 10px 10px
background: white
.patches-view
padding: 10px 20px 10px 0px
.navbar-text
float: left

View file

@ -1,4 +1,14 @@
#editor-level-system-edit-view
nav
margin-bottom: 0
#system-patches
padding: 0 10px 10px
background: white
.patches-view
padding: 10px 20px 10px 0px
.navbar-text
float: left

View file

@ -17,7 +17,7 @@ block content
if user.id != me.id
button.btn.edit-settings-button#enter-espionage-mode 007
if user.get('jobProfile')
if user.get('jobProfile') && allowedToViewJobProfile
- var profile = user.get('jobProfile');
.job-profile-container
.job-profile-row
@ -112,7 +112,11 @@ block content
.project-image(style="background-image: url('/file/" + project.picture + "')")
p= project.name
div!= marked(project.description)
else if allowedToViewJobProfile
.public-profile-container
h2 Loading...
else
.public-profile-container
h2

View file

@ -37,10 +37,12 @@ mixin deltaPanel(delta, conflict)
if delta.conflict && !conflict
.panel-body
strong MERGE CONFLICT WITH
strong(data-i18n="delta.merge_conflict_with") MERGE CONFLICT WITH
+deltaPanel(delta.conflict, true)
.panel-group(id='delta-accordion-'+(counter))
for delta in deltas
+deltaPanel(delta)
if !deltas.length
alert.alert-warning(data-i18n="delta.no_changes") No changes

View file

@ -24,7 +24,7 @@ block header
li
a(href="#editor-level-settings-tab-view", data-toggle="tab", data-i18n="editor.level_tab_settings") Settings
li
a(href="#editor-level-components-tab-view", data-toggle="tab", data-i18n="editor.level_tab_components") Components
a(href="#editor-level-components-tab-view", data-toggle="tab", data-i18n="editor.level_tab_components")#components-tab Components
li
a(href="#editor-level-systems-tab-view", data-toggle="tab", data-i18n="editor.level_tab_systems") Systems
li

View file

@ -10,15 +10,21 @@ block modal-body-content
if dataList
table.table.table-condensed
tr
th
th(data-i18n="general.name") Name
th(data-i18n="general.version") Version
th(data-i18n="general.commit_msg") Commit Message
for data in dataList
tr
td
input(type="checkbox", value=data._id).select
td
a(href="/editor/#{page}/#{data.slug || data._id}")
| #{data.name}
td #{data.version.major}.#{data.version.minor}
td #{data.commitMessage}
div.delta-container
div.delta-view
block modal-footer-content

View file

@ -18,7 +18,7 @@ module.exports = class ProfileView extends View
super options
if @userID is me.id
@user = me
else
else if me.isAdmin() or "employer" in me.get('permissions')
@user = User.getByID(@userID)
@user.fetch()
@listenTo @user, "sync", =>
@ -27,6 +27,7 @@ module.exports = class ProfileView extends View
getRenderData: ->
context = super()
context.user = @user
context.allowedToViewJobProfile = me.isAdmin() or "employer" in me.get('permissions')
context.myProfile = @user.id is context.me.id
context.marked = marked
context.moment = moment

View file

@ -49,7 +49,7 @@ module.exports = class ArticleEditView extends View
data: data
filePath: "db/thang.type/#{@article.get('original')}"
schema: Article.schema
readOnly: true unless me.isAdmin() or @article.hasWriteAccess(me)
readOnly: me.get('anonymous')
callbacks:
change: @pushChangesToPreview
@treema = @$el.find('#article-treema').treema(options)

View file

@ -9,19 +9,62 @@ TEXTDIFF_OPTIONS =
viewType: 1
module.exports = class DeltaView extends CocoView
###
Takes a CocoModel instance (model) and displays changes since the
last save (attributes vs _revertAttributes).
* If headModel is included, will look for and display conflicts with the changes in model.
* If comparisonModel is included, will show deltas between model and comparisonModel instead
of changes within model itself.
###
@deltaCounter: 0
className: "delta-view"
template: template
constructor: (options) ->
super(options)
@model = options.model
@headModel = options.headModel
@expandedDeltas = @model.getExpandedDelta()
@expandedDeltas = []
@skipPaths = options.skipPaths
for modelName in ['model', 'headModel', 'comparisonModel']
continue unless m = options[modelName]
@[modelName] = @supermodel.loadModel(m, 'document').model
@buildDeltas() if @supermodel.finished()
onLoaded: ->
@buildDeltas()
super()
buildDeltas: ->
if @comparisonModel
@expandedDeltas = @model.getExpandedDeltaWith(@comparisonModel)
else
@expandedDeltas = @model.getExpandedDelta()
@filterExpandedDeltas()
if @headModel
@headDeltas = @headModel.getExpandedDelta()
@conflicts = deltasLib.getConflicts(@headDeltas, @expandedDeltas)
filterExpandedDeltas: ->
return unless @skipPaths
for path, i in @skipPaths
@skipPaths[i] = [path] if _.isString(path)
newDeltas = []
for delta in @expandedDeltas
skip = false
for skipPath in @skipPaths
if _.isEqual _.first(delta.dataPath, skipPath.length), skipPath
skip = true
break
newDeltas.push delta unless skip
@expandedDeltas = newDeltas
getRenderData: ->
c = super()
c.deltas = @expandedDeltas

View file

@ -46,11 +46,12 @@ module.exports = class LevelComponentEditView extends View
schema = _.cloneDeep LevelComponent.schema
schema.properties = _.pick schema.properties, (value, key) => key in @editableSettings
schema.required = _.intersection schema.required, @editableSettings
treemaOptions =
supermodel: @supermodel
schema: schema
data: data
readonly: me.get('anonymous')
callbacks: {change: @onComponentSettingsEdited}
@componentSettingsTreema = @$el.find('#edit-component-treema').treema treemaOptions
@componentSettingsTreema.build()
@ -68,6 +69,7 @@ module.exports = class LevelComponentEditView extends View
supermodel: @supermodel
schema: LevelComponent.schema.properties.configSchema
data: @levelComponent.get 'configSchema'
readOnly: me.get('anonymous')
callbacks: {change: @onConfigSchemaEdited}
@configSchemaTreema = @$el.find('#config-schema-treema').treema treemaOptions
@configSchemaTreema.build()
@ -84,13 +86,14 @@ module.exports = class LevelComponentEditView extends View
editorEl = $('<div></div>').text(@levelComponent.get('code')).addClass('inner-editor')
@$el.find('#component-code-editor').empty().append(editorEl)
@editor = ace.edit(editorEl[0])
@editor.setReadOnly(me.get('anonymous'))
session = @editor.getSession()
session.setMode 'ace/mode/coffee'
session.setTabSize 2
session.setNewLineMode = 'unix'
session.setUseSoftTabs true
@editor.on('change', @onEditorChange)
onEditorChange: =>
@levelComponent.set 'code', @editor.getValue()
Backbone.Mediator.publish 'level-component-edited', levelComponent: @levelComponent
@ -104,7 +107,7 @@ module.exports = class LevelComponentEditView extends View
versionHistoryView = new VersionHistoryView {}, @levelComponent.id
@openModalView versionHistoryView
Backbone.Mediator.publish 'level:view-switched', e
startPatchingComponent: (e) ->
@openModalView new SaveVersionModal({model:@levelComponent})
Backbone.Mediator.publish 'level:view-switched', e
@ -117,4 +120,3 @@ module.exports = class LevelComponentEditView extends View
destroy: ->
@editor?.destroy()
super()

View file

@ -14,7 +14,6 @@ module.exports = class ComponentsTabView extends View
className: 'tab-pane'
subscriptions:
'level-thangs-changed': 'onLevelThangsChanged'
'edit-level-component': 'editLevelComponent'
'level-component-edited': 'onLevelComponentEdited'
'level-component-editing-ended': 'onLevelComponentEditingEnded'
@ -24,8 +23,8 @@ module.exports = class ComponentsTabView extends View
'click #create-new-component-button-no-select': 'createNewLevelComponent'
onLoaded: ->
onLevelThangsChanged: (e) ->
thangsData = e.thangsData
refreshLevelThangsTreema: (thangsData) ->
presentComponents = {}
for thang in thangsData
for component in thang.components

View file

@ -29,6 +29,7 @@ module.exports = class EditorLevelView extends View
'click #fork-level-start-button': 'startForkingLevel'
'click #level-history-button': 'showVersionHistory'
'click #patches-tab': -> @patchesView.load()
'click #components-tab': -> @componentsTab.refreshLevelThangsTreema @level.get('thangs')
'click #level-patch-button': 'startPatchingLevel'
'click #level-watch-button': 'toggleWatchLevel'
'click #pop-level-i18n-button': -> @level.populateI18N()

View file

@ -35,6 +35,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
models = if @lastContext.levelNeedsSave then [@level] else []
models = models.concat @lastContext.modifiedComponents
models = models.concat @lastContext.modifiedSystems
models = (m for m in models when m.hasWriteAccess())
for changeEl, i in changeEls
model = models[i]
try
@ -44,6 +45,7 @@ module.exports = class LevelSaveView extends SaveVersionModal
console.error "Couldn't create delta view:", e
shouldSaveEntity: (m) ->
return false unless m.hasWriteAccess()
return true if m.hasLocalChanges()
return true if (m.get('version').major is 0 and m.get('version').minor is 0) or not m.isPublished() and not m.collection
# Sometimes we have two versions: one in a search collection and one with a URL. We only save changes to the latter.

View file

@ -58,7 +58,7 @@ module.exports = class ScriptsTabView extends View
thangIDs: thangIDs
dimensions: @dimensions
supermodel: @supermodel
readOnly: true unless me.isAdmin() or @level.hasWriteAccess(me)
readOnly: me.get('anonymous')
callbacks:
change: @onScriptChanged
nodeClasses:

View file

@ -3,6 +3,7 @@ template = require 'templates/editor/level/settings_tab'
Level = require 'models/Level'
Surface = require 'lib/surface/Surface'
nodes = require './treema_nodes'
{me} = require 'lib/auth'
module.exports = class SettingsTabView extends View
id: 'editor-level-settings-tab-view'
@ -34,7 +35,7 @@ module.exports = class SettingsTabView extends View
supermodel: @supermodel
schema: schema
data: data
readOnly: true unless me.isAdmin() or @level.hasWriteAccess(me)
readOnly: me.get('anonymous')
callbacks: {change: @onSettingsChanged}
thangIDs: thangIDs
nodeClasses:

View file

@ -49,7 +49,7 @@ module.exports = class LevelSystemEditView extends View
schema: schema
data: data
callbacks: {change: @onSystemSettingsEdited}
treemaOptions.readOnly = true unless me.isAdmin()
treemaOptions.readOnly = me.get('anonymous')
@systemSettingsTreema = @$el.find('#edit-system-treema').treema treemaOptions
@systemSettingsTreema.build()
@systemSettingsTreema.open()
@ -67,7 +67,7 @@ module.exports = class LevelSystemEditView extends View
schema: LevelSystem.schema.properties.configSchema
data: @levelSystem.get 'configSchema'
callbacks: {change: @onConfigSchemaEdited}
treemaOptions.readOnly = true unless me.isAdmin()
treemaOptions.readOnly = me.get('anonymous')
@configSchemaTreema = @$el.find('#config-schema-treema').treema treemaOptions
@configSchemaTreema.build()
@configSchemaTreema.open()
@ -83,7 +83,7 @@ module.exports = class LevelSystemEditView extends View
editorEl = $('<div></div>').text(@levelSystem.get('code')).addClass('inner-editor')
@$el.find('#system-code-editor').empty().append(editorEl)
@editor = ace.edit(editorEl[0])
@editor.setReadOnly(not me.isAdmin())
@editor.setReadOnly(me.get('anonymous'))
session = @editor.getSession()
session.setMode 'ace/mode/coffee'
session.setTabSize 2

View file

@ -53,7 +53,7 @@ module.exports = class SystemsTabView extends View
supermodel: @supermodel
schema: Level.schema.properties.systems
data: systems
readOnly: true unless me.isAdmin() or @level.hasWriteAccess(me)
readOnly: me.get('anonymous')
callbacks:
change: @onSystemsChanged
select: @onSystemSelected

View file

@ -37,9 +37,11 @@ module.exports = class PatchModal extends ModalView
if @targetModel.hasWriteAccess()
headModel = @originalSource.clone(false)
headModel.set(@targetModel.attributes)
headModel.loaded = true
pendingModel = @originalSource.clone(false)
pendingModel.applyDelta(@patch.get('delta'))
pendingModel.loaded = true
@deltaView = new DeltaView({model:pendingModel, headModel:headModel})
changeEl = @$el.find('.changes-stub')

View file

@ -22,7 +22,6 @@ module.exports = class PatchesView extends CocoView
@patches = new PatchesCollection([], {}, @model, @status)
load: ->
console.log 'load patches view?'
@initPatches()
@patches = @supermodel.loadCollection(@patches, 'patches').model
@listenTo @patches, 'sync', @onPatchesLoaded

View file

@ -50,7 +50,9 @@ module.exports = class EmployersView extends View
renderCandidatesAndSetupScrolling: =>
@render()
$(".nano").nanoScroller()
if window.location.hash.length is 25
if window.history?.state?.lastViewedCandidateID
$(".nano").nanoScroller({scrollTo:$("#" + window.history.state.lastViewedCandidateID)})
else if window.location.hash.length is 25
$(".nano").nanoScroller({scrollTo:$(window.location.hash)})
sortTable: ->
@ -175,8 +177,13 @@ module.exports = class EmployersView extends View
onCandidateClicked: (e) ->
id = $(e.target).closest('tr').data('candidate-id')
window.location.hash = id
if id
if window.history
oldState = _.cloneDeep window.history.state ? {}
oldState["lastViewedCandidateID"] = id
window.history.replaceState(oldState,"")
else
window.location.hash = id
url = "/account/profile/#{id}"
app.router.navigate url, {trigger: true}
else

View file

@ -38,9 +38,9 @@ module.exports = class RootView extends CocoView
# force the browser to scroll to the hash
# also messes with the browser history, so perhaps come up with a better solution
super()
hash = location.hash
location.hash = ''
location.hash = hash
#hash = location.hash
#location.hash = ''
#location.hash = hash
@renderScrollbar()
#@$('.antiscroll-wrap').antiscroll() # not yet, buggy

View file

@ -20,6 +20,7 @@ module.exports = class EmployerSignupView extends View
"click #contract-agreement-button": "agreeToContract"
"click #create-account-button": "createAccount"
"click .login-link": "setHashToOpenModalAutomatically"
"keydown": "checkForFormSubmissionEnterPress"
constructor: (options) ->
@ -78,6 +79,9 @@ module.exports = class EmployerSignupView extends View
handleAgreementFailure: (error) ->
alert "There was an error signing the contract. Please contact team@codecombat.com with this error: #{error.responseText}"
checkForFormSubmissionEnterPress: (e) ->
if e.which is 13 then @createAccount(e)
createAccount: (e) =>
window.tracker?.trackEvent 'Finished Employer Signup'
e.stopPropagation()

View file

@ -1,6 +1,7 @@
ModalView = require 'views/kinds/ModalView'
template = require 'templates/modal/versions'
tableTemplate = require 'templates/kinds/table'
DeltaView = require 'views/editor/delta'
class VersionsViewCollection extends Backbone.Collection
url: ""
@ -20,6 +21,9 @@ module.exports = class VersionsModalView extends ModalView
id: ""
url: ""
page: ""
events:
'change input.select': 'onSelectionChanged'
constructor: (options, @ID, @model) ->
super options
@ -36,6 +40,18 @@ module.exports = class VersionsModalView extends ModalView
@startsLoading = false
@render()
onSelectionChanged: ->
rows = @$el.find 'input.select:checked'
deltaEl = @$el.find '.delta-view'
@removeSubView(@deltaView) if @deltaView
@deltaView = null
if rows.length isnt 2 then return
laterVersion = new @model(_id:$(rows[0]).val())
earlierVersion = new @model(_id:$(rows[1]).val())
@deltaView = new DeltaView({model:earlierVersion, comparisonModel:laterVersion, skipPaths:['_id','version', 'commitMessage', 'parent', 'created', 'slug', 'index']})
@insertSubView(@deltaView, deltaEl)
getRenderData: (context={}) ->
context = super(context)
context.page = @page

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

@ -38,7 +38,7 @@ module.exports = class LadderView extends RootView
super(options)
@level = @supermodel.loadModel(new Level(_id:@levelID), 'level').model
@sessions = @supermodel.loadCollection(new LevelSessionsCollection(levelID), 'your_sessions').model
@teams = []
onLoaded: ->

View file

@ -17,9 +17,9 @@ module.exports = class Problem
@removeMarkerRange()
buildAnnotation: ->
return unless @aetherProblem.ranges
return unless @aetherProblem.range
text = @aetherProblem.message.replace /^Line \d+: /, ''
start = @aetherProblem.ranges[0][0]
start = @aetherProblem.range[0]
@annotation =
row: start.row,
column: start.col,
@ -33,8 +33,8 @@ module.exports = class Problem
$(@ace.container).append @alertView.el
buildMarkerRange: ->
return unless @aetherProblem.ranges
[start, end] = @aetherProblem.ranges[0]
return unless @aetherProblem.range
[start, end] = @aetherProblem.range
clazz = "problem-marker-#{@aetherProblem.level}"
@markerRange = new Range start.row, start.col, end.row, end.col
@markerRange.start = @ace.getSession().getDocument().createAnchor @markerRange.start

Some files were not shown because too many files have changed in this diff Show more