Merge remote-tracking branch 'cocomaster/master'

This commit is contained in:
Andrew Wong 2014-02-16 22:30:22 -08:00
commit 3bf05ad5c4
150 changed files with 3308 additions and 1927 deletions
CONTRIBUTING.md
app
assets
images/level
javascripts/workers
lib
locale
models
styles
templates
treema-ext.coffee
views

View file

@ -4,4 +4,4 @@
It just grants us a non-exclusive license to use your contribution and certifies you have the right to contribute the code you submit. For both our sakes, we need this before we can accept a pull request. Don't worry, it's super easy.
For more info, see [http://codecombat.com/legal](http://codecombat.com/legal).
For more info, see [http://codecombat.com/legal](http://codecombat.com/legal).

Binary file not shown.

After

(image error) Size: 5.2 KiB

View file

@ -64,7 +64,7 @@ self.transferableSupported = transferableSupported = ->
if args.level
self.world.loadFromLevel args.level, true
self.goalManager = new GoalManager self.world
self.goalManager.setGoals args.goals
self.goalManager.setGoals args.level?.goals or args.goals
self.goalManager.setCode args.userCodeMap
self.goalManager.worldGenerationWillBegin()
self.world.setGoalManager self.goalManager

View file

@ -33,7 +33,7 @@ class Media
class AudioPlayer extends CocoClass
subscriptions:
'play-sound': (e) -> @playInterfaceSound e.trigger
'play-sound': (e) -> @playInterfaceSound e.trigger, e.volume
constructor: () ->
super()
@ -70,17 +70,17 @@ class AudioPlayer extends CocoClass
filename = "/file/interface/#{name}#{@ext}"
@preloadSound filename, name
playInterfaceSound: (name) ->
playInterfaceSound: (name, volume=1) ->
filename = "/file/interface/#{name}#{@ext}"
if filename of cache and createjs.Sound.loadComplete filename
@playSound name
@playSound name, volume
createjs.Sound.play name
else
@preloadInterfaceSounds [name] unless filename of cache
@soundsToPlayWhenLoaded[name] = true
playSound: (name) ->
createjs.Sound.play name, {volume: me.get('volume')}
@soundsToPlayWhenLoaded[name] = volume
playSound: (name, volume=1) ->
createjs.Sound.play name, {volume: me.get('volume') * volume}
# # TODO: load Interface sounds somehow, somewhere, somewhen
@ -108,8 +108,8 @@ class AudioPlayer extends CocoClass
return if not media
media.loaded = true
media.progress = 1.0
if @soundsToPlayWhenLoaded[media.name]
@playSound media.name
if volume = @soundsToPlayWhenLoaded[media.name]
@playSound media.name, volume
@soundsToPlayWhenLoaded[media.name] = false
@notifyProgressChanged()
@ -124,9 +124,3 @@ class AudioPlayer extends CocoClass
module.exports = new AudioPlayer()
average = (numbers) ->
return 0 if numbers.length is 0
sum = 0
sum += num for num in numbers
return sum / numbers.length

View file

@ -6,7 +6,7 @@ makeScopeName = -> "class-scope-#{classCount++}"
module.exports = class CocoClass
subscriptions: {}
shortcuts: {}
# setup/teardown
constructor: ->
@ -19,13 +19,15 @@ module.exports = class CocoClass
destroy: ->
# teardown subscriptions, prevent new ones
@destroyed = true
@stopListening?()
@unsubscribeAll()
@stopListeningToShortcuts()
@[key] = undefined for key of @
@destroyed = true
@destroy = ->
# subscriptions
listenToSubscriptions: ->
# for initting subscriptions
return unless Backbone?.Mediator?
@ -46,7 +48,7 @@ module.exports = class CocoClass
for channel, func of @subscriptions
func = utils.normalizeFunc(func, @)
Backbone.Mediator.unsubscribe(channel, func, @)
# keymaster shortcuts
listenToShortcuts: ->
@ -54,7 +56,7 @@ module.exports = class CocoClass
for shortcut, func of @shortcuts
func = utils.normalizeFunc(func, @)
key(shortcut, @scope, _.bind(func, @))
stopListeningToShortcuts: ->
return unless key?
key.deleteScope(@scope)
key.deleteScope(@scope)

View file

@ -76,6 +76,3 @@ module.exports = GPlusHandler = class GPlusHandler extends CocoClass
storage.save(CURRENT_USER_KEY, model.attributes)
window.location.reload()
})
destroy: ->
super()

View file

@ -1,4 +1,5 @@
{now} = require './world/world_utils'
{now} = require 'lib/world/world_utils'
World = require 'lib/world/world'
## Uncomment to imitate IE9 (and in world_utils.coffee)
#window.Worker = null
@ -11,19 +12,40 @@ module.exports = class God
@lastID = (if @lastID? then @lastID + 1 else Math.floor(@ids.length * Math.random())) % @ids.length
@ids[@lastID]
maxAngels: 2 # how many concurrent web workers to use; if set past 8, make up more names
worldWaiting: false # whether we're waiting for a worker to free up and run the world
constructor: (@world, @level) ->
constructor: (options) ->
@id = God.nextID()
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
@angels = []
@firstWorld = true
Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
@fillWorkerPool = _.throttle @fillWorkerPool, 3000, leading: false
@fillWorkerPool()
onTomeCast: (e) ->
return if @dead
@spells = e.spells
@createWorld()
fillWorkerPool: =>
return unless Worker
@workerPool ?= []
if @workerPool.length < @maxWorkerPoolSize
@workerPool.push @createWorker()
if @workerPool.length < @maxWorkerPoolSize
@fillWorkerPool()
getWorker: ->
@fillWorkerPool()
worker = @workerPool?.shift()
return worker if worker
@createWorker()
createWorker: ->
new Worker '/javascripts/workers/worker_world.js'
getAngel: ->
freeAngel = null
for angel in @angels
@ -67,7 +89,7 @@ module.exports = class God
@worldWaiting = true
return
angel.worker.postMessage {func: 'runWorld', args: {
worldName: @world.name
worldName: @level.name
userCodeMap: @getUserCodeMap()
level: @level
firstWorld: @firstWorld
@ -81,7 +103,7 @@ module.exports = class God
@latestWorldCreation = worldCreation
@latestGoalStates = 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.constructor.deserialize serialized, @world.classMap, @lastSerializedWorldFrames, worldCreation, @finishBeholdingWorld
World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, worldCreation, @finishBeholdingWorld
window.BOX2D_ENABLED = true
@lastSerializedWorldFrames = serialized.frames
@ -89,7 +111,7 @@ module.exports = class God
newWorld.findFirstChangedFrame @world
@world = newWorld
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)
Backbone.Mediator.publish('god:new-world-created', world: @world, firstWorld: @firstWorld, errorCount: errorCount, goalStates: @latestGoalStates, team: me.team)
for scriptNote in @world.scriptNotes
Backbone.Mediator.publish scriptNote.channel, scriptNote.event
@goalManager?.world = newWorld
@ -109,7 +131,10 @@ module.exports = class God
angel.destroy() for angel in @angels
@dead = true
Backbone.Mediator.unsubscribe('tome:cast-spells', @onTomeCast, @)
@goalManager.destroy()
@goalManager = null
@fillWorkerPool = null
@simulateWorld = null
#### Bad code for running worlds on main thread (profiling / IE9) ####
simulateWorld: =>
@ -133,7 +158,7 @@ module.exports = class God
@latestGoalStates = @testGM?.getGoalStates()
serialized = @testWorld.serialize().serializedWorld
window.BOX2D_ENABLED = false
@testWorld.constructor.deserialize serialized, @world.classMap, @lastSerializedWorldFrames, @t0, @finishBeholdingWorld
World.deserialize serialized, @worldClassMap, @lastSerializedWorldFrames, @t0, @finishBeholdingWorld
window.BOX2D_ENABLED = true
@lastSerializedWorldFrames = serialized.frames
@ -168,7 +193,7 @@ class Angel
@spawnWorker()
spawnWorker: ->
@worker = new Worker '/javascripts/workers/worker_world.js'
@worker = @god.getWorker()
@listen()
enslave: ->
@ -183,16 +208,21 @@ class Angel
@started = null
clearInterval @purgatoryTimer
@purgatoryTimer = null
@worker?.terminate()
@worker = null
if @worker
worker = @worker
_.defer -> worker.terminate()
@worker.removeEventListener 'message', @onWorkerMessage
@worker = null
@
abort: ->
return unless @worker
@abortTimeout = _.delay @terminate, @abortTimeoutDuration
@worker.postMessage {func: 'abort'}
terminate: =>
@worker?.terminate()
@worker?.removeEventListener 'message', @onWorkerMessage
@worker = null
return if @dead
@free()
@ -200,7 +230,12 @@ class Angel
destroy: ->
@dead = true
@finishBeholdingWorld = null
@abort()
@terminate = null
@testWorker = null
@condemnWorker = null
@onWorkerMessage = null
testWorker: =>
@worker.postMessage {func: 'reportIn'}
@ -211,25 +246,24 @@ class Angel
@abort()
listen: ->
@worker.addEventListener 'message', (event) =>
switch event.data.type
when 'new-world'
@god.beholdWorld @, event.data.serialized, event.data.goalStates
when 'world-load-progress-changed'
Backbone.Mediator.publish 'god:world-load-progress-changed', event.data unless @dead
when 'console-log'
console.log "|" + @god.id + "'s " + @id + "|", event.data.args...
when 'user-code-problem'
@god.angelUserCodeProblem @, event.data.problem
when 'abort'
#console.log @id, "aborted."
clearTimeout @abortTimeout
@free()
@god.angelAborted @
if @god.dead
@worker.terminate()
@worker = null
when 'reportIn'
clearTimeout @condemnTimeout
else
console.log "Unsupported message:", event.data
@worker.addEventListener 'message', @onWorkerMessage
onWorkerMessage: (event) =>
switch event.data.type
when 'new-world'
@god.beholdWorld @, event.data.serialized, event.data.goalStates
when 'world-load-progress-changed'
Backbone.Mediator.publish 'god:world-load-progress-changed', event.data unless @dead
when 'console-log'
console.log "|" + @god.id + "'s " + @id + "|", event.data.args...
when 'user-code-problem'
@god.angelUserCodeProblem @, event.data.problem
when 'abort'
#console.log @id, "aborted."
clearTimeout @abortTimeout
@free()
@god.angelAborted @
when 'reportIn'
clearTimeout @condemnTimeout
else
console.log "Unsupported message:", event.data

View file

@ -21,6 +21,7 @@ module.exports = class LevelBus extends Bus
'thang-code-ran': 'onCodeRan'
'level-show-victory': 'onVictory'
'tome:spell-changed': 'onSpellChanged'
'tome:spell-created': 'onSpellCreated'
constructor: ->
super(arguments...)
@ -32,7 +33,7 @@ module.exports = class LevelBus extends Bus
@fireScriptsRef = @fireRef?.child('scripts')
setSession: (@session) ->
@session.on('change:multiplayer', @onMultiplayerChanged)
@session.on 'change:multiplayer', @onMultiplayerChanged, @
onPoint: ->
return true unless @session?.get('multiplayer')
@ -91,12 +92,26 @@ module.exports = class LevelBus extends Bus
code = @session.get('code')
code ?= {}
parts = e.spell.spellKey.split('/')
code[parts[0]] ?= {}
code[parts[0]][parts[1]] = e.spell.getSource()
@changedSessionProperties.code = true
@session.set({'code': code})
@saveSession()
onSpellCreated: (e) ->
return unless @onPoint()
spellTeam = e.spell.team
@teamSpellMap[spellTeam] ?= []
unless e.spell.spellKey in @teamSpellMap[spellTeam]
@teamSpellMap[spellTeam].push e.spell.spellKey
@changedSessionProperties.teamSpells = true
@session.set({'teamSpells': @teamSpellMap})
@saveSession()
onScriptStateChanged: (e) ->
return unless @onPoint()
@fireScriptsRef?.update(e)
@ -198,7 +213,7 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.chat = true
@saveSession()
onMultiplayerChanged: =>
onMultiplayerChanged: ->
@changedSessionProperties.multiplayer = true
@session.updatePermissions()
@changedSessionProperties.permissions = true
@ -217,3 +232,15 @@ module.exports = class LevelBus extends Bus
# don't let what the server returns overwrite changes since the save began
tempSession = new LevelSession _id:@session.id
tempSession.save(patch, {patch: true})
destroy: ->
@session.off 'change:multiplayer', @onMultiplayerChanged, @
super()
setTeamSpellMap: (spellMap) ->
@teamSpellMap = spellMap
console.log @teamSpellMap
@changedSessionProperties.teamSpells = true
@session.set({'teamSpells': @teamSpellMap})
@saveSession()

View file

@ -22,79 +22,94 @@ module.exports = class LevelLoader extends CocoClass
subscriptions:
'god:new-world-created': 'loadSoundsForWorld'
constructor: (@levelID, @supermodel, @sessionID) ->
constructor: (options) ->
super()
@supermodel = options.supermodel
@levelID = options.levelID
@sessionID = options.sessionID
@opponentSessionID = options.opponentSessionID
@team = options.team
@headless = options.headless
@loadSession()
@loadLevelModels()
@loadAudio()
@playJingle()
setTimeout (=> @update()), 1 # lets everything else resolve first
_.defer @update # Lets everything else resolve first
playJingle: ->
return if @headless
jingles = ["ident_1", "ident_2"]
AudioPlayer.playInterfaceSound jingles[Math.floor Math.random() * jingles.length]
# Session Loading
loadSession: ->
url = if @sessionID then "/db/level_session/#{@sessionID}" else "/db/level/#{@levelID}/session"
if @sessionID
url = "/db/level_session/#{@sessionID}"
else
url = "/db/level/#{@levelID}/session"
url += "?team=#{@team}" if @team
@session = new LevelSession()
@session.url = -> url
@session.fetch()
@session.once 'sync', @onSessionLoaded
onSessionLoaded: =>
# Unless you specify cache:false, sometimes the browser will use a cached session
# and players will 'lose' code
@session.fetch({cache:false})
@session.once 'sync', @onSessionLoaded, @
if @opponentSessionID
@opponentSession = new LevelSession()
@opponentSession.url = "/db/level_session/#{@opponentSessionID}"
@opponentSession.fetch()
@opponentSession.once 'sync', @onSessionLoaded, @
sessionsLoaded: ->
@session.loaded and ((not @opponentSession) or @opponentSession.loaded)
onSessionLoaded: ->
# TODO: maybe have all non versioned models do this? Or make it work to PUT/PATCH to relative urls
@session.url = -> '/db/level.session/' + @id
@update()
if @session.loaded
@session.url = -> '/db/level.session/' + @id
@update() if @sessionsLoaded()
# Supermodel (Level) Loading
loadLevelModels: ->
@supermodel.once 'loaded-all', @onSupermodelLoadedAll
@supermodel.on 'loaded-one', @onSupermodelLoadedOne
@supermodel.once 'error', @onSupermodelError
@supermodel.on 'loaded-one', @onSupermodelLoadedOne, @
@supermodel.once 'error', @onSupermodelError, @
@level = @supermodel.getModel(Level, @levelID) or new Level _id: @levelID
levelID = @levelID
headless = @headless
@supermodel.shouldPopulate = (model) =>
@supermodel.shouldPopulate = (model) ->
# if left unchecked, the supermodel would load this level
# and every level next on the chain. This limits the population
handles = [model.id, model.get 'slug']
return model.constructor.className isnt "Level" or @levelID in handles
return model.constructor.className isnt "Level" or levelID in handles
@supermodel.shouldLoadProjection = (model) ->
return true if headless and model.constructor.className is 'ThangType'
false
@supermodel.populateModel @level
onSupermodelError: =>
onSupermodelError: ->
msg = $.i18n.t('play_level.level_load_error',
defaultValue: "Level could not be loaded.")
@$el.html('<div class="alert">' + msg + '</div>')
onSupermodelLoadedOne: (e) =>
@notifyProgress()
# if e.model.type() is 'ThangType'
# thangType = e.model
# options = {async: true}
# if thangType.get('name') is 'Wizard'
# options.colorConfig = me.get('wizard')?.colorConfig or {}
# building = thangType.buildSpriteSheet options
# if building
# @spriteSheetsToBuild += 1
# thangType.on 'build-complete', =>
# @spriteSheetsBuilt += 1
# @notifyProgress()
onSupermodelLoadedAll: =>
@trigger 'loaded-supermodel'
@stopListening(@supermodel)
onSupermodelLoadedOne: (e) ->
@update()
# Things to do when either the Session or Supermodel load
update: ->
update: =>
@notifyProgress()
return if @updateCompleted
return unless @supermodel.finished() and @session.loaded
return unless @supermodel.finished() and @sessionsLoaded()
@denormalizeSession()
@loadLevelSounds()
app.tracker.updatePlayState(@level, @session)
@ -124,6 +139,7 @@ module.exports = class LevelLoader extends CocoClass
@buildSpriteSheets()
buildSpriteSheets: ->
return if @headless
thangTypes = {}
thangTypes[tt.get('name')] = tt for tt in @supermodel.getModels(ThangType)
@ -152,16 +168,18 @@ module.exports = class LevelLoader extends CocoClass
return unless building
console.log 'Building:', thangType.get('name'), options
@spriteSheetsToBuild += 1
thangType.on 'build-complete', =>
thangType.once 'build-complete', =>
@spriteSheetsBuilt += 1
@notifyProgress()
# Initial Sound Loading
loadAudio: ->
return if @headless
AudioPlayer.preloadInterfaceSounds ["victory"]
loadLevelSounds: ->
return if @headless
scripts = @level.get 'scripts'
return unless scripts
@ -178,6 +196,7 @@ module.exports = class LevelLoader extends CocoClass
# Dynamic sound loading
loadSoundsForWorld: (e) ->
return if @headless
world = e.world
thangTypes = @supermodel.getModels(ThangType)
for [spriteName, message] in world.thangDialogueSounds()
@ -188,16 +207,19 @@ module.exports = class LevelLoader extends CocoClass
# everything else sound wise is loaded as needed as worlds are generated
allDone: ->
@supermodel.finished() and @session.loaded and @spriteSheetsBuilt is @spriteSheetsToBuild
@supermodel.finished() and @sessionsLoaded() and @spriteSheetsBuilt is @spriteSheetsToBuild
progress: ->
return 0 unless @level.loaded
overallProgress = 0
supermodelProgress = @supermodel.progress()
overallProgress += supermodelProgress * 0.7
overallProgress += 0.1 if @session.loaded
spriteMapProgress = if supermodelProgress is 1 then 0.2 else 0
spriteMapProgress *= @spriteSheetsBuilt / @spriteSheetsToBuild if @spriteSheetsToBuild
overallProgress += 0.1 if @sessionsLoaded()
if @headless
spriteMapProgress = 0.2
else
spriteMapProgress = if supermodelProgress is 1 then 0.2 else 0
spriteMapProgress *= @spriteSheetsBuilt / @spriteSheetsToBuild if @spriteSheetsToBuild
overallProgress += spriteMapProgress
return overallProgress
@ -207,6 +229,7 @@ module.exports = class LevelLoader extends CocoClass
@trigger 'loaded-all' if @progress() is 1
destroy: ->
@world = null # don't hold onto garbage
@supermodel.off 'loaded-one', @onSupermodelLoadedOne
@world = null # don't hold onto garbage
@update = null
super()

View file

@ -20,6 +20,9 @@ module.exports = class CocoRouter extends Backbone.Router
'db/*path': 'routeToServer'
'file/*path': 'routeToServer'
'play/level/:levelID/leaderboard/:teamID/:startRank/:endRank': 'getPaginatedLevelRank'
'play/level/:levelID/player/:playerID': 'getPlayerLevelInfo'
# most go through here
'*name': 'general'
@ -27,6 +30,13 @@ module.exports = class CocoRouter extends Backbone.Router
general: (name) ->
@openRoute(name)
getPaginatedLevelRank: (levelID,teamID,startRank,endRank) ->
return
getPlayerLevelInfo: (levelID,playerID) ->
return
editorModelView: (modelName, slugOrId, subview) ->
modulePrefix = "views/editor/#{modelName}/"
suffix = subview or (if slugOrId then 'edit' else 'home')
@ -119,7 +129,7 @@ module.exports = class CocoRouter extends Backbone.Router
view.render()
closeCurrentView: ->
window.currentModal?.hide()
window.currentModal?.hide?()
return unless window.currentView?
if window.currentView.cache
window.currentView.scrollY = window.scrollY

View file

@ -16,18 +16,18 @@ module.exports.genericFailure = (jqxhr) ->
error = module.exports.parseServerError(jqxhr.responseText)
message = error.message
message = error.property + ' ' + message if error.property
res = errorModalTemplate(
status:jqxhr.status
statusText:jqxhr.statusText
message: message
)
console.warn(jqxhr.status, jqxhr.statusText, error)
existingForm = $('.form-inline:visible:first')
existingForm = $('.form:visible:first')
if existingForm[0]
missingErrors = applyErrorsToForm(existingForm, [error])
for error in missingErrors
existingForm.append($('<div class="alert"></div>').text(error.message))
else
res = errorModalTemplate(
status:jqxhr.status
statusText:jqxhr.statusText
message: message
)
showErrorModal(res)
module.exports.backboneFailure = (model, jqxhr, options) ->

View file

@ -120,8 +120,8 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
@run() unless @worldLoading
destroy: ->
super()
@onEndAll()
super()
# TRIGGERERING NOTES
@ -182,7 +182,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
# STARTING NOTES
run: =>
run: ->
# catch all for analyzing the current state and doing whatever needs to happen next
return if @scriptInProgress
@skipAhead()

View file

@ -0,0 +1,241 @@
SuperModel = require 'models/SuperModel'
LevelLoader = require 'lib/LevelLoader'
GoalManager = require 'lib/world/GoalManager'
God = require 'lib/God'
module.exports = class Simulator
constructor: ->
@retryDelayInSeconds = 10
@taskURL = '/queue/scoring'
fetchAndSimulateTask: =>
$.ajax
url: @taskURL
type: "GET"
error: @handleFetchTaskError
success: @setupSimulationAndLoadLevel
handleFetchTaskError: (errorData) =>
console.log "There were no games to score. Error: #{JSON.stringify errorData}"
console.log "Retrying in #{@retryDelayInSeconds}"
@simulateAnotherTaskAfterDelay()
simulateAnotherTaskAfterDelay: =>
retryDelayInMilliseconds = @retryDelayInSeconds * 1000
_.delay @fetchAndSimulateTask, retryDelayInMilliseconds
setupSimulationAndLoadLevel: (taskData) =>
@task = new SimulationTask(taskData)
@supermodel = new SuperModel()
@god = new God maxWorkerPoolSize: 1, maxAngels: 1 # Start loading worker.
@levelLoader = new LevelLoader supermodel: @supermodel, levelID: @task.getLevelName(), sessionID: @task.getFirstSessionID(), headless: true
@levelLoader.once 'loaded-all', @simulateGame
simulateGame: =>
@assignWorldAndLevelFromLevelLoaderAndDestroyIt()
@setupGod()
try
@commenceSimulationAndSetupCallback()
catch err
console.log "There was an error in simulation(#{err}). Trying again in #{@retryDelayInSeconds} seconds"
@simulateAnotherTaskAfterDelay()
assignWorldAndLevelFromLevelLoaderAndDestroyIt: ->
@world = @levelLoader.world
@level = @levelLoader.level
@levelLoader.destroy()
@levelLoader = null
setupGod: ->
@god.level = @level.serialize @supermodel
@god.worldClassMap = @world.classMap
@setupGoalManager()
@setupGodSpells()
setupGoalManager: ->
@god.goalManager = new GoalManager @world
@god.goalManager.goals = @fetchGoalsFromWorldNoteChain()
@god.goalManager.goalStates = @manuallyGenerateGoalStates()
commenceSimulationAndSetupCallback: ->
@god.createWorld()
Backbone.Mediator.subscribeOnce 'god:new-world-created', @processResults, @
processResults: (simulationResults) ->
taskResults = @formTaskResultsObject simulationResults
@sendResultsBackToServer taskResults
sendResultsBackToServer: (results) =>
$.ajax
url: @taskURL
data: results
type: "PUT"
success: @handleTaskResultsTransferSuccess
error: @handleTaskResultsTransferError
complete: @cleanupAndSimulateAnotherTask
handleTaskResultsTransferSuccess: (result) ->
console.log "Task registration result: #{JSON.stringify result}"
handleTaskResultsTransferError: (error) ->
console.log "Task registration error: #{JSON.stringify error}"
cleanupAndSimulateAnotherTask: =>
@cleanupSimulation()
@fetchAndSimulateTask()
cleanupSimulation: ->
@god.destroy()
@god = null
@world = null
@level = null
formTaskResultsObject: (simulationResults) ->
taskResults =
taskID: @task.getTaskID()
receiptHandle: @task.getReceiptHandle()
calculationTime: 500
sessions: []
for session in @task.getSessions()
sessionResult =
sessionID: session.sessionID
sessionChangedTime: session.sessionChangedTime
metrics:
rank: @calculateSessionRank session.sessionID, simulationResults.goalStates, @task.generateTeamToSessionMap()
taskResults.sessions.push sessionResult
return taskResults
calculateSessionRank: (sessionID, goalStates, teamSessionMap) ->
humansDestroyed = goalStates["destroy-humans"].status is "success"
ogresDestroyed = goalStates["destroy-ogres"].status is "success"
if humansDestroyed is ogresDestroyed
return 0
else if humansDestroyed and teamSessionMap["ogres"] is sessionID
return 0
else if humansDestroyed and teamSessionMap["ogres"] isnt sessionID
return 1
else if ogresDestroyed and teamSessionMap["humans"] is sessionID
return 0
else
return 1
fetchGoalsFromWorldNoteChain: -> return @god.goalManager.world.scripts[0].noteChain[0].goals.add
manuallyGenerateGoalStates: ->
goalStates =
"destroy-humans":
keyFrame: 0
killed:
"Human Base": false
status: "incomplete"
"destroy-ogres":
keyFrame:0
killed:
"Ogre Base": false
status: "incomplete"
setupGodSpells: ->
@generateSpellsObject()
@god.spells = @spells
generateSpellsObject: ->
@currentUserCodeMap = @task.generateSpellKeyToSourceMap()
@spells = {}
for thang in @level.attributes.thangs
continue if @thangIsATemplate thang
@generateSpellKeyToSourceMapPropertiesFromThang thang
thangIsATemplate: (thang) ->
for component in thang.components
continue unless @componentHasProgrammableMethods component
for methodName, method of component.config.programmableMethods
return true if @methodBelongsToTemplateThang method
return false
componentHasProgrammableMethods: (component) -> component.config? and _.has component.config, 'programmableMethods'
methodBelongsToTemplateThang: (method) -> typeof method is 'string'
generateSpellKeyToSourceMapPropertiesFromThang: (thang) =>
for component in thang.components
continue unless @componentHasProgrammableMethods component
for methodName, method of component.config.programmableMethods
spellKey = @generateSpellKeyFromThangIDAndMethodName thang.id, methodName
@createSpellAndAssignName spellKey, methodName
@createSpellThang thang, method, spellKey
@transpileSpell thang, spellKey, methodName
generateSpellKeyFromThangIDAndMethodName: (thang, methodName) ->
spellKeyComponents = [thang.id, methodName]
spellKeyComponents[0] = _.string.slugify spellKeyComponents[0]
spellKeyComponents.join '/'
createSpellAndAssignName: (spellKey, spellName) ->
@spells[spellKey] ?= {}
@spells[spellKey].name = spellName
createSpellThang: (thang, method, spellKey) ->
@spells[spellKey].thangs ?= {}
@spells[spellKey].thangs[thang.id] ?= {}
@spells[spellKey].thangs[thang.id].aether = @createAether @spells[spellKey].name, method
transpileSpell: (thang, spellKey, methodName) ->
slugifiedThangID = _.string.slugify thang.id
source = @currentUserCodeMap[slugifiedThangID]?[methodName] ? ""
@spells[spellKey].thangs[thang.id].aether.transpile source
createAether: (methodName, method) ->
aetherOptions =
functionName: methodName
protectAPI: false
includeFlow: false
return new Aether aetherOptions
class SimulationTask
constructor: (@rawData) ->
getLevelName: ->
levelName = @rawData.sessions?[0]?.levelID
return levelName if levelName?
@throwMalformedTaskError "The level name couldn't be deduced from the task."
generateTeamToSessionMap: ->
teamSessionMap = {}
for session in @rawData.sessions
@throwMalformedTaskError "Two players share the same team" if teamSessionMap[session.team]?
teamSessionMap[session.team] = session.sessionID
teamSessionMap
throwMalformedTaskError: (errorString) ->
throw new Error "The task was malformed, reason: #{errorString}"
getFirstSessionID: -> @rawData.sessions[0].sessionID
getTaskID: -> @rawData.taskID
getReceiptHandle: -> @rawData.receiptHandle
getSessions: -> @rawData.sessions
generateSpellKeyToSourceMap: ->
spellKeyToSourceMap = {}
for session in @rawData.sessions
teamSpells = session.teamSpells[session.team]
_.merge spellKeyToSourceMap, _.pick(session.code, teamSpells)
commonSpells = session.teamSpells["common"]
_.merge spellKeyToSourceMap, _.pick(session.code, commonSpells) if commonSpells?
spellKeyToSourceMap

View file

@ -11,5 +11,5 @@ module.exports.load = (key) ->
module.exports.save = (key, value) ->
s = JSON.stringify(value)
localStorage.setItem(key, s)
module.exports.remove = (key) -> localStorage.removeItem key
module.exports.remove = (key) -> localStorage.removeItem key

View file

@ -148,7 +148,21 @@ module.exports = class Camera extends CocoClass
onMouseScrolled: (e) ->
ratio = 1 + 0.05 * Math.sqrt(Math.abs(e.deltaY))
ratio = 1 / ratio if e.deltaY > 0
@zoomTo @target, @zoom * ratio, 0
newZoom = @zoom * ratio
if e.surfacePos
# zoom based on mouse position, adjusting the target so the point under the mouse stays the same
mousePoint = @canvasToSurface(e.surfacePos)
ratioPosX = (mousePoint.x - @surfaceViewport.x) / @surfaceViewport.width
ratioPosY = (mousePoint.y - @surfaceViewport.y) / @surfaceViewport.height
newWidth = @canvasWidth / newZoom
newHeight = @canvasHeight / newZoom
newTargetX = mousePoint.x - (newWidth * ratioPosX) + (newWidth / 2)
newTargetY = mousePoint.y - (newHeight * ratioPosY) + (newHeight / 2)
target = {x: newTargetX, y:newTargetY}
else
target = @target
@zoomTo target, newZoom, 0
onLevelRestarted: ->
@setBounds(@firstBounds)
@ -186,7 +200,7 @@ module.exports = class Camera extends CocoClass
# Target is either just a {x, y} pos or a display object with {x, y} that might change; surface coordinates.
time = 0 if @instant
newTarget ?= {x:0, y:0}
newTarget = (@newTarget or @target) if @locked
newTarget = (@newTarget or @target) if @locked
newZoom = Math.min((Math.max @minZoom, newZoom), MAX_ZOOM)
return if @zoom is newZoom and newTarget is newTarget.x and newTarget.y is newTarget.y
@ -199,15 +213,13 @@ module.exports = class Camera extends CocoClass
@tweenProgress = 0.01
createjs.Tween.get(@)
.to({tweenProgress: 1.0}, time, createjs.Ease.getPowInOut(3))
.call @onTweenEnd
.call @finishTween
else
@target = newTarget
@zoom = newZoom
@updateZoom true
onTweenEnd: => @finishTween()
finishTween: (abort=false) =>
createjs.Tween.removeTweens(@)
return unless @newTarget
@ -260,3 +272,8 @@ module.exports = class Camera extends CocoClass
@locked = true
unlock: ->
@locked = false
destroy: ->
createjs.Tween.removeTweens @
@finishTween = null
super()

View file

@ -4,6 +4,7 @@ Camera = require './Camera'
Mark = require './Mark'
Label = require './Label'
AudioPlayer = require 'lib/AudioPlayer'
{me} = require 'lib/auth'
# We'll get rid of this once level's teams actually have colors
healthColors =
@ -76,9 +77,12 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@buildFromSpriteSheet @buildSpriteSheet()
destroy: ->
super()
mark.destroy() for name, mark of @marks
label.destroy() for name, label of @labels
@imageObject?.off 'animationend', @playNextAction
@playNextAction = null
@displayObject?.off()
super()
toString: -> "<CocoSprite: #{@thang?.id}>"
@ -107,7 +111,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@displayObject.sprite = @
@displayObject.layerPriority = @thangType.get 'layerPriority'
@displayObject.name = @thang?.spriteName or @thangType.get 'name'
@imageObject.on 'animationend', @onActionEnd
@imageObject.on 'animationend', @playNextAction
##################################################
# QUEUEING AND PLAYING ACTIONS
@ -125,10 +129,9 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@currentRootAction = action
@playNextAction()
onActionEnd: (e) => @playNextAction()
onSurfaceTicked: (e) -> @age += e.dt
playNextAction: ->
playNextAction: =>
@playAction(@actionQueue.splice(0,1)[0]) if @actionQueue.length
playAction: (action) ->
@ -149,7 +152,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
hide: ->
@hiding = true
@updateAlpha()
show: ->
@hiding = false
@updateAlpha()
@ -165,6 +168,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@updateStats()
@updateMarks()
@updateLabels()
@updateGold()
cache: ->
bounds = @imageObject.getBounds()
@ -409,7 +413,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
sound = e.sound ? AudioPlayer.soundForDialogue e.message, @thangType.get 'soundTriggers'
@instance?.stop()
if @instance = @playSound sound, false
@instance.addEventListener "complete", => Backbone.Mediator.publish 'dialogue-sound-completed'
@instance.addEventListener "complete", -> Backbone.Mediator.publish 'dialogue-sound-completed'
@notifySpeechUpdated e
onClearDialogue: (e) ->
@ -429,9 +433,20 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@notifySpeechUpdated blurb: blurb
label.update() for name, label of @labels
updateGold: ->
# TODO: eventually this should be moved into some sort of team-based update
# rather than an each-thang-that-shows-gold-per-team thing.
return if @thang.gold is @lastGold
gold = Math.floor @thang.gold
return if gold is @lastGold
@lastGold = gold
Backbone.Mediator.publish 'surface:gold-changed', {team: @thang.team, gold: gold}
playSounds: (withDelay=true, volume=1.0) ->
for event in @thang.currentEvents ? []
@playSound event, withDelay, volume
if event is 'pay-bounty-gold' and @thang.bountyGold > 25 and @thang.team isnt me.team
AudioPlayer.playInterfaceSound 'coin_1', 0.25
if @thang.actionActivated and (action = @thang.getActionName()) isnt 'say'
@playSound action, withDelay, volume
if @thang.sayMessage and withDelay # don't play sayMessages while scrubbing, annoying

View file

@ -21,9 +21,6 @@ module.exports = class Dimmer extends CocoClass
@highlightedThangIDs = []
@sprites = {}
destroy: ->
super()
toString: -> "<Dimmer>"
build: ->

View file

@ -19,6 +19,8 @@ module.exports = class Mark extends CocoClass
destroy: ->
@mark?.parent?.removeChild @mark
@markSprite?.destroy()
@sprite = null
super()
toString: -> "<Mark #{@name}: Sprite #{@sprite?.thang?.id ? 'None'}>"
@ -119,6 +121,7 @@ module.exports = class Mark extends CocoClass
markSprite = new CocoSprite @thangType, @thangType.spriteOptions
markSprite.queueAction 'idle'
@mark = markSprite.displayObject
@markSprite = markSprite
update: (pos=null) ->
return false unless @on

View file

@ -7,14 +7,14 @@ CROSSFADE_LENGTH = 1500
module.exports = class MusicPlayer extends CocoClass
currentMusic: null
standingBy: null
subscriptions:
'level-play-music': 'onPlayMusic'
'audio-player:loaded': 'onAudioLoaded'
constructor: ->
super(arguments...)
me.on('change:music', @onMusicSettingChanged, @)
super arguments...
me.on 'change:music', @onMusicSettingChanged, @
onAudioLoaded: ->
@onPlayMusic(@standingBy) if @standingBy
@ -29,12 +29,12 @@ module.exports = class MusicPlayer extends CocoClass
AudioPlayer.preloadSound(src)
@standingBy = e
return
@standingBy = null
if @currentMusic
f = -> @stop()
createjs.Tween.get(@currentMusic).to({volume:0.0}, CROSSFADE_LENGTH).call(f)
@currentMusic = createjs.Sound.play(src, 'none', 0, 0, -1, 0.3) if src and e.play
return unless @currentMusic
@currentMusic.volume = 0.0
@ -48,4 +48,8 @@ module.exports = class MusicPlayer extends CocoClass
return unless @currentMusic
createjs.Tween.removeTweens(@currentMusic)
@currentMusic.volume = if me.get('music') then 1.0 else 0.0
destroy: ->
me.off 'change:music', @onMusicSettingChanged, @
super()

View file

@ -7,8 +7,8 @@ module.exports = class PointChooser extends CocoClass
@options.stage.addEventListener 'stagemousedown', @onMouseDown
destroy: ->
super()
@options.stage.removeEventListener 'stagemousedown', @onMouseDown
super()
# Called also from WorldSelectModal
setPoint: (@point) ->

View file

@ -9,10 +9,10 @@ module.exports = class RegionChooser extends CocoClass
@options.stage.addEventListener 'stagemouseup', @onMouseUp
destroy: ->
super()
@options.stage.removeEventListener 'stagemousedown', @onMouseDown
@options.stage.removeEventListener 'stagemousemove', @onMouseMove
@options.stage.removeEventListener 'stagemouseup', @onMouseUp
super()
onMouseDown: (e) =>
return unless key.shift

View file

@ -35,10 +35,10 @@ module.exports = class SpriteBoss extends CocoClass
@spriteSheetCache = {}
destroy: ->
super()
@removeSprite sprite for thangID, sprite of @sprites
@targetMark?.destroy()
@selectionMark?.destroy()
super()
toString: -> "<SpriteBoss: #{@sprites.length} sprites>"
@ -144,8 +144,8 @@ module.exports = class SpriteBoss extends CocoClass
removeSprite: (sprite) ->
sprite.displayObject.parent.removeChild sprite.displayObject
sprite.destroy()
delete @sprites[sprite.thang.id]
sprite.destroy()
updateSounds: ->
sprite.playSounds() for thangID, sprite of @sprites # hmm; doesn't work for sprites which we didn't add yet in adjustSpriteExistence
@ -222,7 +222,7 @@ module.exports = class SpriteBoss extends CocoClass
selectThang: (thangID, spellName=null) ->
@selectSprite null, @sprites[thangID], spellName
selectSprite: (e, sprite=null, spellName=null) =>
selectSprite: (e, sprite=null, spellName=null) ->
return if e and (@disabled or @selectLocked) # Ignore clicks for selection/panning/wizard movement while disabled or select is locked
worldPos = sprite?.thang?.pos
worldPos ?= @camera.canvasToWorld {x: e.originalEvent.rawX, y: e.originalEvent.rawY} if e
@ -252,10 +252,12 @@ module.exports = class SpriteBoss extends CocoClass
# Marks
updateSelection: ->
if @selectedSprite and (not @selectedSprite.thang.exists or not @world.getThangByID @selectedSprite.thang.id)
if @selectedSprite?.thang and (not @selectedSprite.thang.exists or not @world.getThangByID @selectedSprite.thang.id)
@selectSprite null, null, null
@selectionMark?.toggle false
@updateTarget()
return unless @selectionMark
@selectedSprite = null unless @selectedSprite?.thang
@selectionMark.toggle @selectedSprite?
@selectionMark.setSprite @selectedSprite
@selectionMark.update()

View file

@ -79,8 +79,8 @@ module.exports = Surface = class Surface extends CocoClass
@initAudio()
destroy: ->
super()
@dead = true
@camera?.destroy()
createjs.Ticker.removeEventListener("tick", @tick)
createjs.Sound.stop()
layer.destroy() for layer in @layers
@ -89,6 +89,19 @@ module.exports = Surface = class Surface extends CocoClass
@dimmer?.destroy()
@stage.clear()
@musicPlayer?.destroy()
@stage.removeAllChildren()
@stage.removeEventListener 'stagemousemove', @onMouseMove
@stage.removeEventListener 'stagemousedown', @onMouseDown
@stage.removeAllEventListeners()
@stage.enableDOMEvents false
@stage.enableMouseOver 0
@playScrubbedSounds = null
@onMouseMove = null
@onMouseDown = null
@tick = null
@canvas.off 'mousewheel', @onMouseWheel
@onMouseWheel = null
super()
setWorld: (@world) ->
@worldLoaded = true
@ -248,14 +261,14 @@ module.exports = Surface = class Surface extends CocoClass
onSetLetterbox: (e) ->
@setDisabled e.on
onSetPlaying: (e) =>
onSetPlaying: (e) ->
@playing = (e ? {}).playing ? true
if @playing and @currentFrame >= (@world.totalFrames - 5)
@currentFrame = 0
if @fastForwarding and not @playing
@setProgress @currentFrame / @world.totalFrames
onSetTime: (e) =>
onSetTime: (e) ->
toFrame = @currentFrame
if e.time?
@worldLifespan = @world.totalFrames / @world.frameRate
@ -317,6 +330,7 @@ module.exports = Surface = class Surface extends CocoClass
@stage = new createjs.Stage(@canvas[0])
canvasWidth = parseInt(@canvas.attr('width'), 10)
canvasHeight = parseInt(@canvas.attr('height'), 10)
@camera?.destroy()
@camera = new Camera canvasWidth, canvasHeight
@layers.push @surfaceLayer = new Layer name: "Surface", layerPriority: 0, transform: Layer.TRANSFORM_SURFACE, camera: @camera
@layers.push @surfaceTextLayer = new Layer name: "Surface Text", layerPriority: 1, transform: Layer.TRANSFORM_SURFACE_TEXT, camera: @camera
@ -328,7 +342,7 @@ module.exports = Surface = class Surface extends CocoClass
@stage.enableMouseOver(10)
@stage.addEventListener 'stagemousemove', @onMouseMove
@stage.addEventListener 'stagemousedown', @onMouseDown
@hookUpZoomControls()
@canvas.on 'mousewheel', @onMouseWheel
@hookUpChooseControls() if @options.choosing
console.log "Setting fps", @world.frameRate unless @world.frameRate is 30
createjs.Ticker.setFPS @world.frameRate
@ -419,6 +433,7 @@ module.exports = Surface = class Surface extends CocoClass
# uh
onMouseMove: (e) =>
@mouseSurfacePos = {x:e.stageX, y:e.stageY}
return if @disabled
Backbone.Mediator.publish 'surface:mouse-moved', x: e.stageX, y: e.stageY
@ -427,12 +442,15 @@ module.exports = Surface = class Surface extends CocoClass
onBackground = not @stage.hitTest e.stageX, e.stageY
Backbone.Mediator.publish 'surface:stage-mouse-down', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e
hookUpZoomControls: ->
@canvas.bind 'mousewheel', (e) =>
# https://github.com/brandonaaron/jquery-mousewheel
e.preventDefault()
return if @disabled
Backbone.Mediator.publish 'surface:mouse-scrolled', deltaX: e.deltaX, deltaY: e.deltaY unless @disabled
onMouseWheel: (e) =>
# https://github.com/brandonaaron/jquery-mousewheel
e.preventDefault()
return if @disabled
event =
deltaX: e.deltaX
deltaY: e.deltaY
surfacePos: @mouseSurfacePos
Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled
hookUpChooseControls: ->
chooserOptions = stage: @stage, surfaceLayer: @surfaceLayer, camera: @camera, restrictRatio: @options.choosing is 'ratio-region'

View file

@ -183,7 +183,7 @@ module.exports = class WizardSprite extends IndieSprite
Takes into account whether the wizard is in transit or not, and the bobbing up and down.
Eventually will also adjust based on where other wizards are.
"""
@targetPos = @targetSprite.thang.pos if @targetSprite
@targetPos = @targetSprite.thang.pos if @targetSprite?.thang
pos = _.clone(@targetPos)
pos.z = @defaultPos().z + @getBobOffset()
@adjustPositionToSideOfTarget(pos) if @targetSprite # be off to the side depending on placement in world
@ -213,7 +213,7 @@ module.exports = class WizardSprite extends IndieSprite
targetPos.y += @spriteYOffset
faceTarget: ->
if @targetSprite
if @targetSprite?.thang
@pointToward(@targetSprite.thang.pos)
updateMarks: ->

View file

@ -46,7 +46,6 @@ module.exports.Trailmaster = class Trailmaster
clock: 0
constructor: (@camera) ->
@listener = (e) => @tick(e)
tick: ->
@clock += 1

View file

@ -15,7 +15,7 @@ module.exports = class GoalManager extends CocoClass
nextGoalID: 0
constructor: (@world) ->
constructor: (@world, @initialGoals) ->
super()
@init()
@ -25,6 +25,7 @@ module.exports = class GoalManager extends CocoClass
@userCodeMap = {} # @userCodeMap.thangID.methodName.aether.raw = codeString
@thangTeams = {}
@initThangTeams()
@addGoal goal for goal in @initialGoals if @initialGoals
initThangTeams: ->
return unless @world
@ -34,8 +35,6 @@ module.exports = class GoalManager extends CocoClass
@thangTeams[thang.team].push(thang.id)
subscriptions:
'level-add-goals': 'onAddGoals'
'level-remove-goals': 'onRemoveGoals'
'god:new-world-created': 'onNewWorldCreated'
'level:restarted': 'onLevelRestarted'
@ -46,25 +45,15 @@ module.exports = class GoalManager extends CocoClass
'world:thang-collected-item': 'onThangCollectedItem'
'world:ended': 'onWorldEnded'
onLevelRestarted: =>
onLevelRestarted: ->
@goals = []
@goalStates = {}
@userCodeMap = {}
@notifyGoalChanges()
@addGoal goal for goal in @initialGoals if @initialGoals
# INTERFACE AND LIFETIME OVERVIEW
# main instance receives goal updates from the script manager
onAddGoals: (e) =>
return unless e.worldName is @world.name
goals = e.goals
@addGoal(goal) for goal in goals
onRemoveGoals: (e) =>
if e.goal in @goals
@goals.remove(e.goal)
delete @goalStates[e.goal]
# world generator gets current goals from the main instance
getGoals: -> @goals
@ -79,7 +68,7 @@ module.exports = class GoalManager extends CocoClass
func = @backgroundSubscriptions[channel]
func = utils.normalizeFunc(func, @)
return unless func
func(event, frameNumber)
func.call(@, event, frameNumber)
# after world generation, generated goal states
# are grabbed to send back to main instance
@ -88,7 +77,7 @@ module.exports = class GoalManager extends CocoClass
# main instance gets them and updates their existing goal states,
# passes the word along
onNewWorldCreated: (e) =>
onNewWorldCreated: (e) ->
@updateGoalStates(e.goalStates) if e.goalStates?
@world = e.world
@ -101,6 +90,7 @@ module.exports = class GoalManager extends CocoClass
# IMPLEMENTATION DETAILS
addGoal: (goal) ->
goal = _.cloneDeep(goal)
goal.id = @nextGoalID++ if not goal.id
return if @goalStates[goal.id]?
@goals.push(goal)
@ -135,48 +125,47 @@ module.exports = class GoalManager extends CocoClass
status: null # should eventually be either 'success', 'failure', or 'incomplete'
keyFrame: 0 # when it became a 'success' or 'failure'
}
@initGoalState(state, [goal.killThangs, goal.saveThangs], 'killed')
@initGoalState(state, [goal.getToLocations?.who, goal.keepFromLocations?.who], 'arrived')
@initGoalState(state, [goal.leaveOffSides?.who, goal.keepFromLeavingOffSides?.who], 'left')
@initGoalState(state, [goal.collectThangs?.who, goal.keepFromCollectingThangs?.who], 'collected')
@goalStates[goal.id] = state
onThangDied: (e, frameNumber) =>
onThangDied: (e, frameNumber) ->
for goal in @goals ? []
@checkKillThangs(goal.id, goal.killThangs, e.thang, frameNumber) if goal.killThangs?
@checkKillThangs(goal.id, goal.saveThangs, e.thang, frameNumber) if goal.saveThangs?
checkKillThangs: (goalID, targets, thang, frameNumber) =>
checkKillThangs: (goalID, targets, thang, frameNumber) ->
return unless thang.id in targets or thang.team in targets
@updateGoalState(goalID, thang.id, 'killed', frameNumber)
onThangTouchedGoal: (e, frameNumber) =>
onThangTouchedGoal: (e, frameNumber) ->
for goal in @goals ? []
@checkArrived(goal.id, goal.getToLocations.who, goal.getToLocations.targets, e.actor.id, e.touched.id, frameNumber) if goal.getToLocations?
@checkArrived(goal.id, goal.keepFromLocations.who, goal.keepFromLocations.targets, e.actor.id, e.touched.id, frameNumber) if goal.keepFromLocations?
checkArrived: (goalID, who, targets, thangID, touchedID, frameNumber) =>
checkArrived: (goalID, who, targets, thangID, touchedID, frameNumber) ->
return unless touchedID in targets
return unless thangID in who
@updateGoalState(goalID, thangID, 'arrived', frameNumber)
onThangLeftMap: (e, frameNumber) =>
onThangLeftMap: (e, frameNumber) ->
for goal in @goals ? []
@checkLeft(goal.id, goal.leaveOffSides.who, goal.leaveOffSides.sides, e.thang.id, e.side, frameNumber) if goal.leaveOffSides?
@checkLeft(goal.id, goal.keepFromLeavingOffSides.who, goal.keepFromLeavingOffSides.sides, e.thang.id, e.side, frameNumber) if goal.keepFromLeavingOffSides?
checkLeft: (goalID, who, sides, thangID, side, frameNumber) =>
checkLeft: (goalID, who, sides, thangID, side, frameNumber) ->
return if sides and side and not (side in sides)
return unless thangID in who
@updateGoalState(goalID, thangID, 'left', frameNumber)
onThangCollectedItem: (e, frameNumber) =>
onThangCollectedItem: (e, frameNumber) ->
for goal in @goals ? []
@checkCollected(goal.id, goal.collectThangs.who, goal.collectThangs.targets, e.actor.id, e.item.id, frameNumber) if goal.collectThangs?
@checkCollected(goal.id, goal.keepFromCollectingThangs.who, goal.keepFromCollectingThangs.targets, e.actor.id, e.item.id, frameNumber) if goal.keepFromCollectingThangs?
checkCollected: (goalID, who, targets, thangID, itemID, frameNumber) =>
checkCollected: (goalID, who, targets, thangID, itemID, frameNumber) ->
return unless itemID in targets
return unless thangID in who
@updateGoalState(goalID, thangID, 'collected', frameNumber)

View file

@ -1,797 +0,0 @@
{me} = require('lib/auth')
# If we use marked somewhere else, we'll have to make sure to preserve options
marked.setOptions {gfm: true, sanitize: false, smartLists: true, breaks: true}
markedWithImages = (s) ->
s = s.replace /!\[(.*?)\]\((.+)? (\d+) (\d+) ?(.*?)?\)/g, '<img src="/images/docs/$2" alt="$1" title="$1" style="width: $3px; height: $4px;" class="$5"></img>' # setting width/height attrs doesn't prevent flickering, but inline css does
marked(s)
module.exports.getDocsFor = (thang, props, isSnippet=false) ->
docs = []
types = {}
for prop in props ? []
type = if isSnippet then 'snippet' else typeof thang[prop]
(types[type] ?= []).push prop
order = ["function", "object", "string", "number", "boolean", "undefined", "snippet"]
order.push type for type of types when not (type in order)
for type in order
for prop in (types[type] ? [])
docClass = if D.hasOwnProperty prop then D[prop] else Doc
docs.push new docClass(thang, prop, type)
docs
module.exports.hasLevelDocs = (levelID) ->
D[levelID]?
module.exports.getLevelDocs = (levelID, world) ->
levelDocsClass = D[levelID] ? Level
new levelDocsClass world
module.exports.Doc = class Doc
writable: false
owner: "this"
constructor: (@thang, @prop, @type) ->
if @owner isnt "this"
@type = typeof window[@owner][@prop]
@buildShortName()
buildShortName: ->
@shortName = @prop
if @type is 'function' then @shortName += "()"
if @type isnt 'snippet' then @shortName = "#{@owner}.#{@shortName}"
@shorterName = @shortName.replace 'this.', ''
@shortName += ';'
title: ->
typeStr = @type
if @type is 'function' and @owner is 'this'
typeStr = 'method'
if @type isnt 'function' and not @writable
typeStr += ' (read-only)'
nameStr = @shortName
"""<h4>#{nameStr} - <code class=\"prop-type\">#{typeStr}</code></h4>"""
html: ->
s = markedWithImages(@doc())
if @type in ['function', 'snippet']
exampleCode = @example()
if exampleCode.split('\n').length > 1
exampleCode = "```\n#{exampleCode}```"
else
exampleCode = "`#{exampleCode}`"
s += marked("**Example**:\n#{exampleCode}")
args = @args?() or []
if args.length
s += marked("**Arguments**:")
s += (arg.html() for arg in args).join('')
else
s += @value()
s
value: ->
"""
<strong>Current value</strong>:
<code class=\"current-value\" data-prop=\"#{@prop}\">#{@formatValue()}</code>
"""
doc: ->
"""
This does something. I think.
"""
example: ->
s = "#{@owner}.#{@shortName};"
if @type is 'function'
exampleArgs = (arg.example ? arg.default ? arg.name for arg in @args?() ? []).join ', '
s = "#{@owner}.#{@prop}(#{exampleArgs});"
s
formatValue: ->
if @owner is 'this'
v = @thang[@prop]
else
v = window[@owner][@prop]
if @type is 'number' and not isNaN v
if v == Math.round v
return v
return v.toFixed 2
if _.isString v
return "\"#{v}\""
v
module.exports.Arg = class Arg
constructor: (@name, @type, @example, @description, @default) ->
html: ->
s = "`#{@name}`: `#{@type}`"
if @example then s += " (ex: `#{@example}`)"
if @description then s += "\n#{@description}"
if @default? then s += "\n*Default value*: `#{@default}`"
marked s
module.exports.Level = class Level
constructor: (@world) ->
docID: (docName="doc") ->
"level-doc-#{docName}"
html: (docName="doc", title, buttons) ->
forVictory = docName is "victory"
docID = @docID docName
title ?= @world.name + (if forVictory then " Complete" else "")
docHTML = if @[docName] then markedWithImages(@[docName]()) else "No #{docName}. Wail!"
if forVictory
buttonHTML = @victoryButtons()
else
buttonHTML = @buttons(buttons ? ["I'm Ready."])
"""<div id="#{docID}" class="level-doc modal hide fade" tabindex="-1">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">×</button>
<h3>#{title}</h3>
</div>
<div class="modal-body">
#{docHTML}
</div>
<div class="modal-footer">
#{buttonHTML}
</div>
</div>
"""
doc: ->
"""
Venusaur? Oh no, counterattack with whatever you feel like, man!
"""
victory: ->
"""
Detect it, it it no going and you tell me do things I done runnin'.
"""
buttons: (names) ->
buttonHTML = []
for name in names
buttonHTML.push(
"""
<button class="btn btn-primary" data-dismiss="modal">#{name}</button>
"""
)
buttonHTML.join ' '
victoryButtons: ->
buttons = """
<button id="victory-stay" data-dismiss="modal" class="btn">Stay A While</button>
<a href="/" class="btn">Go Home</a>
"""
if me.get 'anonymous'
buttons += """
<button class="btn btn-success sign-up-button">Sign Up for Updates</button>
"""
if @world.nextLevel
buttons += """
<a href="/play/level/#{@world.nextLevel}" class="btn btn-primary" data-dismiss="modal" >Next Level</a>
"""
buttons += """
<div class="share-buttons">
<div class="g-plusone" data-href="http://codecombat.com" data-size="medium"></div>
<div class="fb-like" data-href="http://codecombat.com" data-send="false" data-layout="button_count" data-width="350" data-show-faces="true"></div>
</div>
"""
buttons
D = {} # save typing for all these things
# Markdown: http://daringfireball.net/projects/markdown/syntax
# GitHub Flavored Markdown: https://help.github.com/articles/github-flavored-markdown
# Our Markdown parser and compiler: https://github.com/chjj/marked
# I have extended the img syntax to take width and height (preventing flicker).
D.ifElse = class IfElse extends Doc
buildShortName: ->
@shortName = @shorterName = "if/else"
doc: ->
"""
The `if` control statement lets you choose whether to run the following code based on whether the condition evaluates truthily.
You can add an optional `else` clause to run instead when the condition evaluates falsily.
"""
example: ->
"""
if(2 + 2 === 4) {
// Code here
}
else {
// Code here
}
"""
D.forLoop = class ForLoop extends Doc
buildShortName: ->
@shortName = @shorterName = "for-loop"
doc: ->
"""
The `for` loop lets you run code many times. It has four parts:
* initial setup: `var i = 0;` (run at the beginning)
* loop condition: `i < 10;` (code runs while this is true)
* loop iteration: `i += 1` (runs after every iteration)
* main loop code: `console.log("Counted to", i);`
"""
example: ->
"""
for(var i = 0; i < 10; i += 1) {
// Code here
}
"""
D.whileLoop = class WhileLoop extends Doc
buildShortName: ->
@shortName = @shorterName = "while-loop"
doc: ->
"""
The `while` loop lets you run code many times--as long as the condition is true.
"""
example: ->
"""
var i = 10;
while(i < 10) {
// Code here
i -= 1;
}
"""
D.rotateTo = class RotateTo extends Doc
doc: ->
"""
The `rotateTo` method rotates the #{@thang.spriteName}. ![Rotation of 0° points to the right, 90° points up, etc.](rotate.png 160 160)
Use this method to change the direction that the #{@thang.spriteName} shoots.
"""
args: ->
[new Arg "degrees", "number", "180", "Desired rotation in degrees"]
D.shoot = class Shoot extends Doc
doc: ->
"""
Calling `this.shoot();` makes the #{@thang.spriteName} choose the `shoot` action.
It's equivalent to: `this.setAction(this.actions.shoot);`
"""
D.pos = class Pos extends Doc
doc: ->
"""
The `x` (horizontal) and `y` (vertical) coordinates of the #{@thang.spriteName}'s center.
"""
D.rotation = class Rotation extends Doc
doc: ->
"""
The #{@thang.spriteName}'s rotation in radians (`0` to `2 * Math.PI`).
Use the `rotateTo` method to set this value.
"""
D.degrees = class Degrees extends Doc
doc: ->
"""
The #{@thang.spriteName}'s rotation in degrees (`0` to `360`).
Use the `rotateTo` method to set this value.
"""
D.attackRange = class AttackRange extends Doc
doc: ->
"""
How far the #{@thang.spriteName}'s attack reaches, in meters.
"""
D.health = class Health extends Doc
doc: ->
"""
How many health points the #{@thang.spriteName} has left.
"""
D.team = class Team extends Doc
doc: ->
"""
What team the #{@thang.spriteName} is on.
"""
D.actions = class Actions extends Doc
doc: ->
"""
The #{@thang.spriteName}'s available actions.
To, say, move, use: `this.setAction("move");`
"""
formatValue: () ->
v = @thang[@prop]
'[' + ("\"#{actionName}\"" for actionName of v).join(', ') + ']'
D.action = class Action extends Doc
doc: ->
"""
The current action the #{@thang.spriteName} is running.
To change this, use the `setAction` method.
"""
D.setAction = class SetAction extends Doc
doc: ->
"""
Sets the action that the #{@thang.spriteName} is running. Only actions in `this.actions` are valid.
"""
#For example, if `this.action` is currently `"idle"` and you want it to move instead, you'd set a target location, then: `this.setAction("move");`
args: ->
exampleAction = "idle"
for action in ["move", "shoot", "attack"]
if @thang.actions[action]
exampleAction = action
break
unless exampleAction
exampleAction = _.some @thang.actions, "name"
exampleAction = "\"#{exampleAction}\""
[new Arg "action", "object", exampleAction, "The action to perform (must be one of `this.actions`)."]
D.target = class Target extends Doc
doc: ->
"""
The current target upon which the #{@thang.spriteName} is running its `action`.
To change this, use the `setTarget` method.
"""
formatValue: () ->
v = @thang[@prop]
if v? then v.toString() else 'null'
D.setTarget = class SetTarget extends Doc
doc: ->
"""
Sets what the #{@thang.spriteName} is targeting with its current `action`, such as an enemy to attack or a position to move to.
"""
#For example, if `this.action` is currently `this.actions.move`, the #{@thang.spriteName} will move toward its `target`; if its action is `this.actions.attack`, it will try to attack its target.
#For some actions, you can also pass `x` and `y` coordinates as a target instead of another unit: `this.setTarget(65, 40);`
args: ->
exampleTarget = "65, 40"
for [methods, code] in [[["getNearestEnemy"], "this.getNearestEnemy()"],
[["pos", "move"], "this.pos.x + 20, this.pos.y - 20"],
[["attack"], "enemy"],
[["shoot"], "enemy"]]
if (m for m in methods when m in @thang.programmableProperties).length
exampleTarget = code
break
[new Arg "target", "object", exampleTarget, "The new target upon which to act."]
D.chooseAction = class ChooseAction extends Doc
doc: ->
"""
The `chooseAction` method is run every frame, allowing the #{@thang.spriteName} a chance to look at the world and decide what action to pursue this frame.
To see available actions, hover over the `actions` property below. To choose an action, say `attack`, you can write: `this.setAction(this.actions.attack);`
"""
D.plan = class Plan extends Doc
doc: ->
"""
The `plan` method (spell) is where you write a sequence of methods (spells) to command the #{@thang.spriteName}.
Type your methods into the Spell Editor below. Hover over your available methods at the bottom to see what they do.
"""
example: ->
"""
this.moveRight();
this.moveRight();
this.attackNearbyEnemy();
"""
D.attack = class Attack extends Doc
doc: ->
"""
The `attack` method takes an enemy `target`, sets the #{@thang.spriteName}'s `target` to that `target` with the `setTarget` method, and sets the #{@thang.spriteName}'s `action` to the `this.actions.attack` action with the `setAction` method.
"""
args: ->
exampleTarget = "enemy"
for [methods, code] in [[["getNearestEnemy"], "this.getNearestEnemy()"],
[["getEnemies"], "this.getEnemies()[0]"]]
if (m for m in methods when m in @thang.programmableProperties).length
exampleTarget = code
break
[new Arg "target", "object", exampleTarget, "The target enemy to attack."]
D.moveXY = class MoveXY extends Doc
doc: ->
"""
The `moveXY` method sets the #{@thang.spriteName}'s `targetPos` to the given `(x, y)` coordinates and also sets the #{@thang.spriteName}'s `action` to `move`.
"""
args: ->
[
new Arg "x", "number", "24", "The x coordinate toward which to move."
new Arg "y", "number", "35", "The y coordinate toward which to move."
]
D.distanceTo = class DistanceTo extends Doc
doc: ->
"""
Returns the distance in meters to the `target` unit from the center of the #{@thang.spriteName}.
"""
args: ->
exampleTarget = "enemy"
for [methods, code] in [#[["getNearestEnemy"], "this.getNearestEnemy()"],
[["getEnemies"], "this.getEnemies()[0]"],
[["getFriends"], "this.getFriends()[0]"]]
if (m for m in methods when m in @thang.programmableProperties).length
exampleTarget = code
break
[new Arg "target", "object", exampleTarget, "The target unit whose distance you want to measure."]
D.getEnemies = class GetEnemies extends Doc
doc: ->
"""
Returns an array of all living enemies within eyesight.
"""
example: ->
"""
var enemies = this.getEnemies();
for(var i = 0; i < enemies.length; ++i) {
var enemy = enemies[i];
// Do something with each enemy here
this.attack(enemy); // Example
}
"""
D.getFriends = class GetFriends extends Doc
doc: ->
"""
Returns an array of all living friends within eyesight.
"""
example: ->
"""
var friends = this.getFriends();
for(var i = 0; i < friends.length; ++i) {
var friend = friends[i];
// Do something with each friend here
this.follow(friend); // Example
}
"""
D.getItems = class GetItems extends Doc
doc: ->
"""
Returns an array of all living items within eyesight.
"""
example: ->
"""
var items = this.getItems()
for(var i = 0; i < items.length; ++i) {
var item = items[i];
// Do something with each item here
this.move(item.pos); // Example
}
"""
D.attackNearbyEnemy = class GetNearbyEnemy extends Doc
doc: ->
"""
Attacks any enemy within #{@thang.attackNearbyEnemyRange ? 5} meters of the #{@thang.spriteName}.
"""
D.getNearestEnemy = class GetNearestEnemy extends Doc
doc: ->
"""
Returns the closest living enemy, or `null` if there aren\'t any.
"""
example: ->
"""
var enemy = this.getNearestEnemy();
"""
D.getNearestFriend = class GetNearestFriend extends Doc
doc: ->
"""
Returns the closest living friend, or `null` if there aren\'t any.
"""
example: ->
"""
var friend = this.getNearestFriend();
"""
D.getNearestCombtaant = class GetNearestCombatant extends Doc
doc: ->
"""
Returns the closest living friend or foe, or `null` if there aren\'t any.
"""
example: ->
"""
var enemy = this.getNearestCombatant();
"""
D.attackXY = class AttackXY extends Doc
doc: ->
"""
The `attackXY` method makes the #{@thang.spriteName} attack the ground at the given `(x, y)` coordinates.
"""
args: ->
[
new Arg "x", "number", "24", "The x coordinate to attack."
new Arg "y", "number", "35", "The y coordinate to attack."
]
D.patrol = class Patrol extends Doc
doc: ->
"""
The `patrol` method causes the #{@thang.spriteName} to move between the given waypoints in a loop. When combined with code to `attack` nearby enemies, you can use it to guard an area.
"""
args: ->
[new Arg "patrolPoints", "array", "[{x: 15, y: 45}, {x: 30, y: 40}, {x: 25, y: 35}]", "An array of positions to move between."]
D.attackNearestEnemy = class AttackNearestEnemy extends Doc
doc: ->
"""
The `attackNearestEnemy` method causes the #{@thang.spriteName} to charge at the nearest enemy and try to slay it.
"""
D.moveRight = class MoveRight extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} right by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveRight();
"""
D.moveLeft = class MoveLeft extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} left by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveLeft();
"""
D.moveUp = class MoveUp extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} up by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveUp();
"""
D.moveDown = class MoveDown extends Doc
doc: ->
"""
Moves the #{@thang.spriteName} down by #{@thang.simpleMoveDistance} meters.
"""
example: ->
"""
this.moveDown();
"""
D.say = class Say extends Doc
doc: ->
"""
Makes the #{@thang.spriteName} say the given message. Anything within #{@thang.voiceRange ? 20} meters will hear it.
"""
args: ->
[new Arg "message", "string", "\"Follow me!\"", "The message to say."]
D.chaseAndAttack = class ChaseAndAttack extends Doc
doc: ->
"""
Makes the #{@thang.spriteName} attack `target` if in range, otherwise move to `target`.
"""
args: ->
[new Arg "target", "object", "this.getNearestEnemy()", "The unit to chase and attack."]
D.wait = class Wait extends Doc
doc: ->
"""
The `wait()` method makes the #{@thang.spriteName} wait for a moment before continuing to execute the rest of the code.
It currently doesn't work inside nested functions in a `plan()` method.
"""
args: ->
[
new Arg "duration", "number", "0.1", "Number of seconds to wait."
]
D.addRect = class AddRect extends Doc
doc: ->
"""
The `addRect()` method adds a rectangle centered at the given `(x, y)` coordinate with the given `width` and `height`.
"""
args: ->
[
new Arg "x", "number", "30", "x-coordinate of center of the rectangle."
new Arg "y", "number", "12", "y-coordinate of center of the rectangle."
new Arg "width", "number", "4", "width of the rectangle."
new Arg "height", "number", "24", "height of the rectangle."
]
D.removeRectAt = class RemoveRectAt extends Doc
doc: ->
"""
The `removeRectAt()` method removes a previously added rectangle centered at the given `(x, y)` coordinate.
"""
args: ->
[
new Arg "x", "number", "30", "x-coordinate of center of the rectangle to remove."
new Arg "y", "number", "12", "y-coordinate of center of the rectangle to remove."
]
D.getNavGrid = class GetNavGrid extends Doc
doc: ->
"""
The `getNavGrid()` method returns an undocumented data structure CodeCombat uses in its pathfinding system. Sorry--will improve.
Just use its `grid` property, which is a two-dimensional array with one-meter resolution indicating where the walls are:
```
var grid = this.getNavGrid().grid;
var y = 12;
var x = 30;
var occupied = grid[y][x].length > 0;
```
"""
D.spawnedRectangles = class SpawnedRectangles extends Doc
doc: ->
"""
An array of rectangle objects which have been added with the `addRect()` method.
You can get a rectangle's dimensions, like this:
```
for(var i = 0; i < this.spawnedRectangles.length; ++i) {
var rect = this.spawnedRectangles[i];
// rect.pos.x, rect.pos.y, rect.width, rect.height
}
```
"""
D.pow = class Pow extends Doc
owner: "Math"
doc: ->
"""
Returns `base` to the `exponent` power, that is, <code>base<sup>exponent</sup></code>.
"""
example: ->
"""
Math.pow(7, 2); // returns 49
"""
args: ->
[
new Arg "base", "number", "7", "The base number."
new Arg "exponent", "number", "2", "The exponent used to raise the `base`."
]
D.sqrt = class Sqrt extends Doc
owner: "Math"
doc: ->
"""
Returns the square root of a non-negative number. Equivalent to `Math.pow(x, 0.5)`.
"""
example: ->
"""
Math.sqrt(49); // returns 7
"""
args: ->
[new Arg "x", "number", "49", ""]
D.sin = class Sin extends Doc
owner: "Math"
doc: ->
"""
Returns the sine of a number (between -1 and 1).
"""
example: ->
"""
Math.sin(Math.PI / 4); // returns 2
"""
args: ->
[new Arg "x", "number", "Math.PI / 2", "A number in radians."]
D.cos = class Cos extends Doc
owner: "Math"
doc: ->
"""
Returns the cosine of a number (between -1 and 1).
"""
example: ->
"""
Math.cos(3 * Math.PI / 4); // returns -2
"""
args: ->
[new Arg "x", "number", "Math.PI / 2", "A number in radians."]
D.tan = class Tan extends Doc
owner: "Math"
doc: ->
"""
Returns the tangent of a number (between -1 and 1).
"""
example: ->
"""
Math.tan(Math.PI / 4); // returns 0.9999999999999999
"""
args: ->
[new Arg "x", "number", "Math.PI / 2", "A number in radians."]
D.atan2 = class Atan2 extends Doc
owner: "Math"
doc: ->
"""
Returns the arctangent of the quotient of its arguments--a numeric value between -π and π representing the counterclockwise angle theta of an (x, y) point and the positive X axis.
"""
args: ->
[
new Arg "y", "number", "90", ""
new Arg "x", "number", "15", ""
]
D.PI = class PI extends Doc
owner: "Math"
doc: ->
"""
The ratio of the circumference of a circle to its diameter, approximately 3.14159.
"""
D.random = class Random extends Doc
owner: "Math"
doc: ->
"""
Returns a random number x such that 0 <= x < 1.
"""

View file

@ -2,7 +2,7 @@
class Vector
@className: "Vector"
# Class methods for nondestructively operating
for name in ['add', 'subtract', 'multiply', 'divide']
for name in ['add', 'subtract', 'multiply', 'divide', 'limit', 'normalize']
do (name) ->
Vector[name] = (a, b, useZ) ->
a.copy()[name](b, useZ)

View file

@ -31,21 +31,21 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
about: "Sobre"
contact: "Contate-nos"
twitter_follow: "Seguir"
# employers: "Employers"
employers: "Empregadores"
# versions:
# save_version_title: "Save New Version"
# new_major_version: "New Major Version"
# cla_prefix: "To save changes, first you must agree to our"
# cla_url: "CLA"
# cla_suffix: "."
# cla_agree: "I AGREE"
versions:
save_version_title: "Salvar nova versão"
new_major_version: "Nova versão principal"
cla_prefix: "Para salvar as modificações, primeiro você deve concordar com nosso"
cla_url: "CLA"
cla_suffix: "."
cla_agree: "EU CONCORDO"
login:
sign_up: "Criar conta"
log_in: "Entrar"
log_out: "Sair"
recover: "recuperar sua conta"
recover: "Recuperar sua conta"
recover:
recover_account_title: "Recuperar conta"
@ -122,7 +122,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
new_password_verify: "Confirmação"
email_subscriptions: "Assinaturas para Notícias por Email"
email_announcements: "Notícias"
# email_notifications_description: "Get periodic notifications for your account."
email_notifications_description: "Recebe notificações periódicas em sua conta."
email_announcements_description: "Receba emails com as últimas notícias e desenvolvimentos do CodeCombat."
contributor_emails: "Emails para as Classes de Contribuidores"
contribute_prefix: "Estamos procurando pessoas para se juntar à nossa turma! Confira a nossa "
@ -191,251 +191,252 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
tome_select_a_thang: "Selecione alguém para "
tome_available_spells: "Feitiços Disponíveis"
hud_continue: "Continue (tecle Shift+Space)"
# spell_saved: "Spell Saved"
spell_saved: "Feitiço Salvo"
# admin:
# av_title: "Admin Views"
# av_entities_sub_title: "Entities"
# av_entities_users_url: "Users"
# av_entities_active_instances_url: "Active Instances"
# av_other_sub_title: "Other"
# av_other_debug_base_url: "Base (for debugging base.jade)"
# u_title: "User List"
# lg_title: "Latest Games"
admin:
av_title: "Visualização de Administrador"
av_entities_sub_title: "Entidades"
av_entities_users_url: "Usuários"
av_entities_active_instances_url: "Instâncias Ativas"
av_other_sub_title: "Outro"
av_other_debug_base_url: "Base (para debugar base.jade)"
u_title: "Lista de Usuários"
lg_title: "Últimos Jogos"
# editor:
# main_title: "CodeCombat Editors"
# main_description: "Build your own levels, campaigns, units and educational content. We provide all the tools you need!"
# article_title: "Article Editor"
# article_description: "Write articles that give players overviews of programming concepts which can be used across a variety of levels and campaigns."
# thang_title: "Thang Editor"
# thang_description: "Build units, defining their default logic, graphics and audio. Currently only supports importing Flash exported vector graphics."
# level_title: "Level Editor"
# level_description: "Includes the tools for scripting, uploading audio, and constructing custom logic to create all sorts of levels. Everything we use ourselves!"
# security_notice: "Many major features in these editors are not currently enabled by default. As we improve the security of these systems, they will be made generally available. If you'd like to use these features sooner, "
# contact_us: "contact us!"
# hipchat_prefix: "You can also find us in our"
# hipchat_url: "HipChat room."
# level_some_options: "Some Options?"
# level_tab_thangs: "Thangs"
# level_tab_scripts: "Scripts"
# level_tab_settings: "Settings"
# level_tab_components: "Components"
# level_tab_systems: "Systems"
# level_tab_thangs_title: "Current Thangs"
# level_tab_thangs_conditions: "Starting Conditions"
# level_tab_thangs_add: "Add Thangs"
# level_settings_title: "Settings"
# level_component_tab_title: "Current Components"
# level_component_btn_new: "Create New Component"
# level_systems_tab_title: "Current Systems"
# level_systems_btn_new: "Create New System"
# level_systems_btn_add: "Add System"
# level_components_title: "Back to All Thangs"
# level_components_type: "Type"
# level_component_edit_title: "Edit Component"
# level_system_edit_title: "Edit System"
# create_system_title: "Create New System"
# new_component_title: "Create New Component"
# new_component_field_system: "System"
editor:
main_title: "Editores do CodeCombat"
main_description: "Construa seus próprios níveis, campanhas, unidades e conteúdo educacional. Nós fornecemos todas as ferramentas que você precisa!"
article_title: "Editor de Artigo"
article_description: "Escreva artigos que forneçam aos jogadores explicações sobre conceitos de programação que podem ser utilizados em diversos níveis e campanhas."
thang_title: "Editor de Thang"
thang_description: "Construa unidades, definindo sua lógica padrão, gráfico e áudio. Atualmente só é suportado importação de vetores gráficos exportados do Flash."
level_title: "Editor de Niível"
level_description: "Inclui as ferramentas para codificar, fazer o upload de áudio e construir uma lógica diferente para criar todos os tipos de níveis. Tudo o que nós mesmos utilizamos!"
security_notice: "Muitos recursos principais nestes editores não estão habilitados por padrão atualmente. A maneira que melhoramos a segurança desses sistemas, eles serão colocados a disposição. Se você quiser utilizar um desses recursos mais rápido, "
contact_us: "entre em contato!"
hipchat_prefix: "Você também pode nos encontrar na nossa"
hipchat_url: "Sala do HipChat."
level_some_options: "Algumas Opções?"
level_tab_thangs: "Thangs"
level_tab_scripts: "Scripts"
level_tab_settings: "Configurações"
level_tab_components: "Componentes"
level_tab_systems: "Sistemas"
level_tab_thangs_title: "Thangs Atuais"
level_tab_thangs_conditions: "Condições de Início"
level_tab_thangs_add: "Adicionar Thangs"
level_settings_title: "Configurações"
level_component_tab_title: "Componentess Atuais"
level_component_btn_new: "Criar Novo Componente"
level_systems_tab_title: "Sistemas Atuais"
level_systems_btn_new: "Criar Novo Sistema"
level_systems_btn_add: "Adicionar Sistema"
level_components_title: "Voltar para Lista de Thangs"
level_components_type: "Tipo"
level_component_edit_title: "Editar Componente"
level_system_edit_title: "Editar Sistema"
create_system_title: "Criar Novo Sistema"
new_component_title: "Criar Novo Componente"
new_component_field_system: "Sistema"
# article:
# edit_btn_preview: "Preview"
# edit_article_title: "Edit Article"
article:
edit_btn_preview: "Prever"
edit_article_title: "Editar Artigo "
general:
# and: "and"
and: "e"
or: "ou"
name: "Nome"
# body: "Body"
# version: "Version"
# commit_msg: "Commit Message"
# version_history_for: "Version History for: "
# results: "Results"
# description: "Description"
body: "Principal"
version: "Versão"
commit_msg: "Mensagem do Commit"
version_history_for: "Histórico de Versão para: "
results: "Resultados"
description: "Descrição"
email: "Email"
message: "Mensagem"
# about:
# who_is_codecombat: "Who is CodeCombat?"
# why_codecombat: "Why CodeCombat?"
# who_description_prefix: "together started CodeCombat in 2013. We also created "
# who_description_suffix: "in 2008, growing it to the #1 web and iOS application for learning to write Chinese and Japanese characters."
# who_description_ending: "Now it's time to teach people to write code."
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
# why_paragraph_3_italic: "yay a badge"
# why_paragraph_3_center: "but fun like"
# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
# why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
# why_ending: "And hey, it's free. "
# why_ending_url: "Start wizarding now!"
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."
# jeremy_description: "Customer support mage, usability tester, and community organizer; you've probably already spoken with Jeremy."
# michael_description: "Programmer, sys-admin, and undergrad technical wunderkind, Michael is the person keeping our servers online."
about:
who_is_codecombat: "Quem é CodeCombat?"
why_codecombat: "Por que CodeCombat?"
who_description_prefix: "juntos começamos o CodeCombat em 2013. Noós também criamos "
who_description_suffix: "em 2008, subindo até o 1º lugar entre aplicativos web e para iOS para aprender caracteres chineses e japoneses."
who_description_ending: "Agora é a hora de ensinar as pessoas a escreverem código."
why_paragraph_1: " Quando estava desenvolvendo o Skritter, George não sabia como programar e ficava constantemente frustrado por causa de sua falta de habilidade para implementar suas idéias. Depois, ele tentou aprender, mas as aulas eram muito lentas. Seu colega de quarto, tentando inovar e parar de dar aulas, tentou o Codecademy, mas \"ficou entediado.\" A cada semana um novo amigo começava no Codecademy, e desistia em seguida. Nós percebemos que era o mesmo problema que havíamos resolvido com o Skritter: pessoas aprendendo uma habilidade através de aulas lentas e intensivas quando o que elas precisavam era de prática, rápida e extensa. Nós sabemos como consertar isso."
why_paragraph_2: "Precisa aprender a codificar? Você não precisa de aulas. Você precisa escrever muito código e se divertir fazendo isso."
why_paragraph_3_prefix: "É disso que se trata a programação. Tem que ser divertido. Não divertido como"
why_paragraph_3_italic: "oba uma insígnia"
why_paragraph_3_center: "mas divertido como"
why_paragraph_3_italic_caps: "NÃO MÃE EU PRECISO TERMINAR ESSE NÍVEL!"
why_paragraph_3_suffix: "É por isso que o CodeCombat é um jogo multiplayer, não uma aula que imita um jogo. Nós não iremos parar até você não conseguir parar--mas agora, isso é uma coisa boa."
why_paragraph_4: "Se você vai se viciar em algum jogo, fique viciado nesse e se torne um dos magos da era da tecnologia."
why_ending: "E é de graça. "
why_ending_url: "Comece a feitiçaria agora!"
george_description: "CEO, um cara de negócios, web designer, designer de jogos, e campeão em iniciar programadores em qualquer lugar."
scott_description: "Programador extraordinário, arquiteto de software, mago da cozinha e mestre de finanças. Scott é o racional."
nick_description: "Mago da programação, feiticeiro da motivação excêntrica e experimentador doido. Nick pode fazer qualquer coisa e escolheu desenvolver o CodeCombat."
jeremy_description: "Mago em suporte ao consumidor, testador de usabilidade, e organizador da comunidade; você provavelmente já falou com o Jeremy."
michael_description: "Programador, administrador de sistemas, e um técnico prodígio não graduado, Michael é a pessoa que mantém os servidores funcionando."
# legal:
# page_title: "Legal"
# opensource_intro: "CodeCombat is free to play and completely open source."
# opensource_description_prefix: "Check out "
# github_url: "our GitHub"
# opensource_description_center: "and help out if you like! CodeCombat is built on dozens of open source projects, and we love them. See "
# archmage_wiki_url: "our Archmage wiki"
# opensource_description_suffix: "for a list of the software that makes this game possible."
# practices_title: "Respectful Best Practices"
# practices_description: "These are our promises to you, the player, in slightly less legalese."
# privacy_title: "Privacy"
# privacy_description: "We will not sell any of your personal information. We intend to make money through recruitment eventually, but rest assured we will not distribute your personal information to interested companies without your explicit consent."
# security_title: "Security"
# security_description: "We strive to keep your personal information safe. As an open source project, our site is freely open to anyone to review and improve our security systems."
# email_title: "Email"
# email_description_prefix: "We will not inundate you with spam. Through"
# email_settings_url: "your email settings"
# email_description_suffix: "or through links in the emails we send, you can change your preferences and easily unsubscribe at any time."
# cost_title: "Cost"
# cost_description: "Currently, CodeCombat is 100% free! One of our main goals is to keep it that way, so that as many people can play as possible, regardless of place in life. If the sky darkens, we might have to charge subscriptions or for some content, but we'd rather not. With any luck, we'll be able to sustain the company with:"
# recruitment_title: "Recruitment"
# recruitment_description_prefix: "Here on CodeCombat, you're going to become a powerful wizardnot just in the game, but also in real life."
# url_hire_programmers: "No one can hire programmers fast enough"
# recruitment_description_suffix: "so once you've sharpened your skills and if you agree, we will demo your best coding accomplishments to the thousands of employers who are drooling for the chance to hire you. They pay us a little, they pay you"
# recruitment_description_italic: "a lot"
# recruitment_description_ending: "the site remains free and everybody's happy. That's the plan."
# copyrights_title: "Copyrights and Licenses"
# contributor_title: "Contributor License Agreement"
# contributor_description_prefix: "All contributions, both on the site and on our GitHub repository, are subject to our"
# cla_url: "CLA"
# contributor_description_suffix: "to which you should agree before contributing."
# code_title: "Code - MIT"
# code_description_prefix: "All code owned by CodeCombat or hosted on codecombat.com, both in the GitHub repository or in the codecombat.com database, is licensed under the"
# mit_license_url: "MIT license"
# code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
# art_title: "Art/Music - Creative Commons "
# art_description_prefix: "All common content is available under the"
# cc_license_url: "Creative Commons Attribution 4.0 International License"
# art_description_suffix: "Common content is anything made generally available by CodeCombat for the purpose of creating Levels. This includes:"
# art_music: "Music"
# art_sound: "Sound"
# art_artwork: "Artwork"
# art_sprites: "Sprites"
# art_other: "Any and all other non-code creative works that are made available when creating Levels."
# art_access: "Currently there is no universal, easy system for fetching these assets. In general, fetch them from the URLs as used by the site, contact us for assistance, or help us in extending the site to make these assets more easily accessible."
# art_paragraph_1: "For attribution, please name and link to codecombat.com near where the source is used or where appropriate for the medium. For example:"
# use_list_1: "If used in a movie or another game, include codecombat.com in the credits."
# use_list_2: "If used on a website, include a link near the usage, for example underneath an image, or in a general attributions page where you might also mention other Creative Commons works and open source software being used on the site. Something that's already clearly referencing CodeCombat, such as a blog post mentioning CodeCombat, does not need some separate attribution."
# art_paragraph_2: "If the content being used is created not by CodeCombat but instead by a user of codecombat.com, attribute them instead, and follow attribution directions provided in that resource's description if there are any."
# rights_title: "Rights Reserved"
# rights_desc: "All rights are reserved for Levels themselves. This includes"
# rights_scripts: "Scripts"
# rights_unit: "Unit configuration"
# rights_description: "Description"
# rights_writings: "Writings"
# rights_media: "Media (sounds, music) and any other creative content made specifically for that Level and not made generally available when creating Levels."
# rights_clarification: "To clarify, anything that is made available in the Level Editor for the purpose of making levels is under CC, whereas the content created with the Level Editor or uploaded in the course of creation of Levels is not."
# nutshell_title: "In a Nutshell"
# nutshell_description: "Any resources we provide in the Level Editor are free to use as you like for creating Levels. But we reserve the right to restrict distribution of the Levels themselves (that are created on codecombat.com) so that they may be charged for in the future, if that's what ends up happening."
# canonical: "The English version of this document is the definitive, canonical version. If there are any discrepencies between translations, the English document takes precedence."
# contribute:
# page_title: "Contributing"
# character_classes_title: "Character Classes"
# introduction_desc_intro: "We have high hopes for CodeCombat."
# introduction_desc_pref: "We want to be where programmers of all stripes come to learn and play together, introduce others to the wonderful world of coding, and reflect the best parts of the community. We can't and don't want to do that alone; what makes projects like GitHub, Stack Overflow and Linux great are the people who use them and build on them. To that end, "
# introduction_desc_github_url: "CodeCombat is totally open source"
# introduction_desc_suf: ", and we aim to provide as many ways as possible for you to take part and make this project as much yours as ours."
# introduction_desc_ending: "We hope you'll join our party!"
# introduction_desc_signature: "- Nick, George, Scott, Michael, and Jeremy"
# alert_account_message_intro: "Hey there!"
# alert_account_message_pref: "To subscribe for class emails, you'll need to "
# alert_account_message_suf: "first."
# alert_account_message_create_url: "create an account"
# archmage_introduction: "One of the best parts about building games is they synthesize so many different things. Graphics, sound, real-time networking, social networking, and of course many of the more common aspects of programming, from low-level database management, and server administration to user facing design and interface building. There's a lot to do, and if you're an experienced programmer with a hankering to really dive into the nitty-gritty of CodeCombat, this class might be for you. We would love to have your help building the best programming game ever."
# class_attributes: "Class Attributes"
# archmage_attribute_1_pref: "Knowledge in "
# archmage_attribute_1_suf: ", or a desire to learn. Most of our code is in this language. If you're a fan of Ruby or Python, you'll feel right at home. It's JavaScript, but with a nicer syntax."
# archmage_attribute_2: "Some experience in programming and personal initiative. We'll help you get oriented, but we can't spend much time training you."
# how_to_join: "How To Join"
# join_desc_1: "Anyone can help out! Just check out our "
# join_desc_2: "to get started, and check the box below to mark yourself as a brave Archmage and get the latest news by email. Want to chat about what to do or how to get more deeply involved? "
# join_desc_3: ", or find us in our "
# join_desc_4: "and we'll go from there!"
# join_url_email: "Email us"
# join_url_hipchat: "public HipChat room"
# more_about_archmage: "Learn More About Becoming A Powerful Archmage"
# archmage_subscribe_desc: "Get emails on new coding opportunities and announcements."
# artisan_introduction_pref: "We must construct additional levels! People be clamoring for more content, and we can only build so many ourselves. Right now your workstation is level one; our level editor is barely usable even by its creators, so be wary. If you have visions of campaigns spanning for-loops to"
# artisan_introduction_suf: "to then this class might be for you."
# artisan_attribute_1: "Any experience in building content like this would be nice, such as using Blizzard's level editors. But not required!"
# artisan_attribute_2: "A hankering to do a whole lot of testing and iteration. To make good levels, you need to take it to others and watch them play it, and be prepared to find a lot of things to fix."
# artisan_attribute_3: "For the time being, endurance en par with an Adventurer. Our Level Editor is super preliminary and frustrating to use. You have been warned!"
# artisan_join_desc: "Use the Level Editor in these steps, give or take:"
# artisan_join_step1: "Read the documentation."
# artisan_join_step2: "Create a new level and explore existing levels."
# artisan_join_step3: "Find us in our public HipChat room for help."
# artisan_join_step4: "Post your levels on the forum for feedback."
# more_about_artisan: "Learn More About Becoming A Creative Artisan"
# artisan_subscribe_desc: "Get emails on level editor updates and announcements."
# adventurer_introduction: "Let's be clear about your role: you are the tank. You're going to take heavy damage. We need people to try out brand-new levels and help identify how to make things better. The pain will be enormous; making good games is a long process and no one gets it right the first time. If you can endure and have a high constitution score, then this class might be for you."
# adventurer_attribute_1: "A thirst for learning. You want to learn how to code and we want to teach you how to code. You'll probably be doing most of the teaching in this case, though."
# adventurer_attribute_2: "Charismatic. Be gentle but articulate about what needs improving, and offer suggestions on how to improve."
# adventurer_join_pref: "Either get together with (or recruit!) an Artisan and work with them, or check the box below to receive emails when there are new levels to test. We'll also be posting about levels to review on our networks like"
# adventurer_forum_url: "our forum"
# adventurer_join_suf: "so if you prefer to be notified those ways, sign up there!"
# more_about_adventurer: "Learn More About Becoming A Brave Adventurer"
# adventurer_subscribe_desc: "Get emails when there are new levels to test."
# scribe_introduction_pref: "CodeCombat isn't just going to be a bunch of levels. It will also include a resource for knowledge, a wiki of programming concepts that levels can hook into. That way rather than each Artisan having to describe in detail what a comparison operator is, they can simply link their level to the Article describing them that is already written for the player's edification. Something along the lines of what the "
# scribe_introduction_url_mozilla: "Mozilla Developer Network"
# scribe_introduction_suf: " has built. If your idea of fun is articulating the concepts of programming in Markdown form, then this class might be for you."
# scribe_attribute_1: "Skill in words is pretty much all you need. Not only grammar and spelling, but able to convey complicated ideas to others."
# contact_us_url: "Contact us"
# scribe_join_description: "tell us a little about yourself, your experience with programming and what sort of things you'd like to write about. We'll go from there!"
# more_about_scribe: "Learn More About Becoming A Diligent Scribe"
# scribe_subscribe_desc: "Get emails about article writing announcements."
# diplomat_introduction_pref: "So, if there's one thing we learned from the "
# diplomat_launch_url: "launch in October"
# diplomat_introduction_suf: "it's that there is sizeable interest in CodeCombat in other countries, particularly Brazil! We're building a corps of translators eager to turn one set of words into another set of words to get CodeCombat as accessible across the world as possible. If you like getting sneak peeks at upcoming content and getting these levels to your fellow nationals ASAP, then this class might be for you."
# diplomat_attribute_1: "Fluency in English and the language you would like to translate to. When conveying complicated ideas, it's important to have a strong grasp in both!"
# diplomat_join_pref: "We've started a lot of initial translations at "
# diplomat_doc_url: "this forum post"
# diplomat_join_suf: "so check it out and add things for your language. Also, check this box below to keep up-to-date on new internationalization developments!"
# more_about_diplomat: "Learn More About Becoming A Great Diplomat"
# diplomat_subscribe_desc: "Get emails about i18n developments and levels to translate."
# ambassador_introduction: "This is a community we're building, and you are the connections. We've got Olark chats, emails, and social networks with lots of people to talk with and help get acquainted with the game and learn from. If you want to help people get involved and have fun, and get a good feel of the pulse of CodeCombat and where we're going, then this class might be for you."
# ambassador_attribute_1: "Communication skills. Be able to identify the problems players are having and help them solve them. Also, keep the rest of us informed about what players are saying, what they like and don't like and want more of!"
# ambassador_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll go from there!"
# ambassador_join_note_strong: "Note"
# ambassador_join_note_desc: "One of our top priorities is to build multiplayer where players having difficulty solving levels can summon higher level wizards to help them. This will be a great way for ambassadors to do their thing. We'll keep you posted!"
# more_about_ambassador: "Learn More About Becoming A Helpful Ambassador"
# ambassador_subscribe_desc: "Get emails on support updates and multiplayer developments."
# counselor_introduction_1: "Do you have life experience? A different perspective on things that can help us decide how to shape CodeCombat? Of all these roles, this will probably take the least time, but individually you may make the most difference. We're on the lookout for wisened sages, particularly in areas like: teaching, game development, open source project management, technical recruiting, entrepreneurship, or design."
# counselor_introduction_2: "Or really anything that is relevant to the development of CodeCombat. If you have knowledge and want to share it to help grow this project, then this class might be for you."
# counselor_attribute_1: "Experience, in any of the areas above or something you think might be helpful."
# counselor_attribute_2: "A little bit of free time!"
# counselor_join_desc: "tell us a little about yourself, what you've done and what you'd be interested in doing. We'll put you in our contact list and be in touch when we could use advice (not too often)."
# more_about_counselor: "Learn More About Becoming A Valuable Counselor"
# changes_auto_save: "Changes are saved automatically when you toggle checkboxes."
# diligent_scribes: "Our Diligent Scribes:"
# powerful_archmages: "Our Powerful Archmages:"
# creative_artisans: "Our Creative Artisans:"
# brave_adventurers: "Our Brave Adventurers:"
# translating_diplomats: "Our Translating Diplomats:"
# helpful_ambassadors: "Our Helpful Ambassadors:"
legal:
page_title: "Jurídico"
opensource_intro: "CodeCombat é grátis para jogar e de código completamente aberto."
opensource_description_prefix: "Confira "
github_url: "nosso GitHub"
opensource_description_center: "e ajude-nos se você gostar! CodeCombat é construído a partir de dúzias de projetos de código aberto, e nós amamos eles. Veja "
archmage_wiki_url: "nossa wiki mágica"
opensource_description_suffix: "para uma lista dos softwares que fazem esse jogo possível."
practices_title: "Respeitáveis Boas Práticas"
practices_description: "Essas são nossas promessas para você, o jogador, de uma maneira menos jurídica."
privacy_title: "Privacidade"
privacy_description: "Nós não venderemos nenhuma de suas informações pessoais. Nós pretendemos ganhar dinheiro através de recrutamento eventualmente, mas fiquem tranquilos que nós não distribuiremos suas informações pessoais para companhias interessadas sem a sua aprovação explícita."
security_title: "Segurança"
security_description: "Nós lutamos para manter suas informações pessoais a salvo.Como um projeto de código aberto, nosso site é aberto para qualquer um rever e melhorar nossos sistemas de segurança."
email_title: "Email"
email_description_prefix: "Nós não iremos te encher de spam. Através"
email_settings_url: "das suas configurações de email"
email_description_suffix: "ou através de links nos emails que enviarmos, você pode trocar as preferências e facilmente se desinscrever a qualquer hora."
cost_title: "Custo"
cost_description: "Atualmente o CodeCombat é 100% grátis. Um dos nossos principais objetivos é mantê-lo dessa forma, para que, o maior número possível de pessoas possa jogar, independente de seu lugar na vida. Se o céu escurecer, nós poderemos ter que cobrar uma assinatura, ou por algum conteúdo, mas preferimos que não. Com alguma sorte, nós seremos capazes de manter a empresa com:"
recruitment_title: "Recrutamento"
recruitment_description_prefix: "Aqui no CodeCombat, você vai se tornar um poderoso feiticeiro, não apenas no jogo, mas também na vida real."
url_hire_programmers: "Ninguém pode contratar programadores rápido o bastante"
recruitment_description_suffix: "então quando você aumentar suas habilidade e, se concordar, vamos demonstrar suas melhores realizações em codificação para os milhares de empregadores que estão babando para ter a chance de contratá-lo. Eles nos pagam um pouco, eles te pagam"
recruitment_description_italic: "muito"
recruitment_description_ending: "o site continua grátis e todo mundo fica feliz. Esse é o plano."
copyrights_title: "Direitos Autorais e Licenças"
contributor_title: "Contrato de Licença de Colaborador"
contributor_description_prefix: "Todos os colaboradores, tanto no nosso site quando no nosso repositório no GitHub estão sujeitos ao nosso"
cla_url: "CLA"
contributor_description_suffix: " para o qual você deve estar de acordo antes de contribuir."
code_title: "Código - MIT"
code_description_prefix: "Todo o código possuído pelo CodeCombat ou hospedado no codecombat.com, tanto no nosso repositório no GitHub como no banco de dados do codecombat.com, é licenciado sob"
mit_license_url: "Licença do MIT"
code_description_suffix: "Isso inclui todo o código nos sistemas e componentes que são disponibilizados pelo CodeCombat para o propósito de criar níveis."
art_title: "Arte/Música - Creative Commons "
art_description_prefix: "Todo o conteúdo comum está disponível sob a"
cc_license_url: "Creative Commons Attribution 4.0 International License"
art_description_suffix: "Conteúdo comum é qualquer coisa disponível pelo CodeCombat com o propósito de criar níveis. Isto inclui:"
art_music: "Música"
art_sound: "Som"
art_artwork: "Obra de arte"
art_sprites: "Sprites"
art_other: "Todo e qualquer outros trabalho criativo não relacionados a código que são disponibilizados para criar níveis."
art_access: "Atualmente não existe um sistema universal e fácil para buscar por essas obras. Em geral, busque-os através das URLs utilizadas pelo site, entre em contato conosco para obter auxílio, ou nos ajude a estender o site para tornar as obras acessíveis mais facilmente."
art_paragraph_1: "Para atribuição, por favor, nomeie e referencie o codecombat.com perto de onde a fonte é utilizada, ou onde for apropriado para o meio. Por exemplo:"
use_list_1: "Se usado em um filme ou outro jogo, inclua codecombat.com nos créditos."
use_list_2: "Se usado em um site, incluir um link perto do local de uso, por exemplo, em baixo de uma imagem, ou em uma página de atribuições gerais onde você pode também mencionar outros trabalhos em Creative Commons e softwares de código aberto que estão sendo usados no site.Algo que já está referenciando claramente o CodeCombat, como um post no blog mencionando o CodeCombat, não precisa de atribuição separada."
art_paragraph_2: "Se o conteúdo que está sendo usado não é criado pelo CodeCombat mas por algum usuário do codecombat.com, você deve referenciá-los seguindo as normas contidas neste documento caso haja alguma."
rights_title: "Direitos Reservados"
rights_desc: "Todos os direitos são reservados para os Níveis em si. Isto inclui"
rights_scripts: "Scripts"
rights_unit: "Configurações de unidades"
rights_description: "Descrição"
rights_writings: "Escritos"
rights_media: "Mídia (sons, música) e qualquer outro conteúdo criativo feito especificamente para esse nível e que não estão disponíveis quando se está criando níveis."
rights_clarification: "Para esclarecer, tudo o que é disponibilizado no Editor de Níveis com o objetivo de criar os níveis está sob CC, enquanto que o conteúdo criado através do Editor de Níveis ou inserido durante o processo de criação dos níveis não é."
nutshell_title: "Em poucas palavras"
nutshell_description: "Todos os recursos que oferecemos no Editor de Níveis é livre para usar como quiser para a criação de níveis. Mas nós nos reservamos o direito de restringir a distribuição dos próprios níveis (que são criados em codecombat.com) para que possam ser cobrados no futuro, se é que isso precise acontecer."
canonical: "A versão em inglês deste documento é a versão canônica definitiva. Se houver discrepâncias entre traduções, o documento em inglês tem precedência."
# classes:
# archmage_title: "Archmage"
# archmage_title_description: "(Coder)"
# artisan_title: "Artisan"
# artisan_title_description: "(Level Builder)"
# adventurer_title: "Adventurer"
# adventurer_title_description: "(Level Playtester)"
# scribe_title: "Scribe"
# scribe_title_description: "(Article Editor)"
# diplomat_title: "Diplomat"
# diplomat_title_description: "(Translator)"
# ambassador_title: "Ambassador"
# ambassador_title_description: "(Support)"
# counselor_title: "Counselor"
# counselor_title_description: "(Expert/Teacher)"
contribute:
page_title: "Contribuindo"
character_classes_title: "Classes de Personagem"
introduction_desc_intro: "Nós temos grandes expectativas para o CodeCombat."
introduction_desc_pref: "Queremos ser o lugar onde os programadores de todos os tipos vêm para aprender e brincarem juntos, introduzir outros ao maravilhoso mundo da codificação, e refletir as melhores partes da comunidade. Não podemos e não queremos fazer isso sozinhos, o que faz de projetos como o GitHub, Stack Overflow e Linux ótimos são as pessoas que os utilizam e constróem sobre eles. Para esse fim, "
introduction_desc_github_url: "CodeCombat é totalmente código aberto"
introduction_desc_suf: ", e nosso objetivo é oferecer quantas maneiras forem possíveis para você participar e fazer deste projeto tanto seu como nosso."
introduction_desc_ending: "Nós esperamos que você se junte a nossa festa!"
introduction_desc_signature: "- Nick, George, Scott, Michael, and Jeremy"
alert_account_message_intro: "Ei!"
alert_account_message_pref: "Para se inscrever para os emails de classe, você vai precisar, "
alert_account_message_suf: "primeiro."
alert_account_message_create_url: "criar uma conta"
archmage_introduction: "Uma das melhores partes sobre a construção de jogos é que eles sintetizam diversas coisas diferentes. Gráficos, som, interação em tempo real, redes sociais, e, claro, muitos dos aspectos mais comuns da programação, desde a gestão em baixo nível de banco de dados, e administração do servidor até interação com o usuário e desenvolvimento da interface. Há muito a fazer, e se você é um programador experiente com um desejo ardente de realmente mergulhar no âmago da questão do CodeCombat, esta classe pode ser para você. Nós gostaríamos de ter sua ajuda para construir o melhor jogo de programação de todos os tempos."
class_attributes: "Atributos da Classe"
archmage_attribute_1_pref: "Conhecimento em "
archmage_attribute_1_suf: ", ou um desejo de aprender. A maioria do nosso código é escrito nessa lingua. Se você é um fã de Ruby ou Python, você vai se sentir em casa. É JavaScript, mas com uma sintaxe mais agradável."
archmage_attribute_2: "Alguma experiência em programação e iniciativa pessoal. Nós vamos ajudá-lo a se orientar, mas não podemos passar muito tempo treinando você."
how_to_join: "Como Participar"
join_desc_1: "Qualquer um pode ajudar! Confira nosso "
join_desc_2: "para começar, e marque a caixa abaixo para marcar-se como um arquimago corajoso e receba as últimas notícias por email. Quer conversar sobre o que fazer ou como se envolver mais profundamente? "
join_desc_3: ", ou encontre-nos em nosso "
join_desc_4: "e começaremos a partir de lá!"
join_url_email: "Envie-nos um email"
join_url_hipchat: "Sala de bate-papo pública no HipChat"
more_about_archmage: "Saiba Mais Sobre Como Se Tornar Um Poderoso Arquimago"
archmage_subscribe_desc: "Receba email sobre novas oportunidades para codificar e anúncios."
artisan_introduction_pref: "Nós devemos contruir níveis adicionais! Pessoas estão clamando por mais conteúdo, e só podemos contruir tantos de nós mesmos. Agora sua estação de trabalho é o nível um; nosso Editor de Níveis é pouco utilizável até mesmo para seus criadores, então fique esperto. Se você tem visões de campanhas abrangendo for-loops para"
artisan_introduction_suf: "para, em seguida, esta classe pode ser para você."
artisan_attribute_1: "Qualquer experiência em construir conteúdo como esse seria legal, como usando os editores de nível da Blizzard. Mas não é obrigatório!"
artisan_attribute_2: "Um desejo ardente de fazer um monte de testes e iteração. Para fazer bons níveis, você precisa levá-lo para os outros e vê-los jogar, e estar preparado para encontrar muitas coisas para consertar."
artisan_attribute_3: "Por enquanto, a resistência em par com um Aventureiro. Nosso Editor de Níveis é super preliminar e frustrante para usar. Você foi avisado!"
artisan_join_desc: "Use o Editor de Níveis nestas etapas, mais ou menos:"
artisan_join_step1: "Leia a documentação."
artisan_join_step2: "Crie um novo nível e explore os níveis existentes."
artisan_join_step3: "Encontre-nos na nossa sala pública no HipChat para ajuda."
artisan_join_step4: "Publique seus níveis no fórum para avaliação."
more_about_artisan: "Saiba Mais Sobre Como Se Tornar Um Artesão Criativo"
artisan_subscribe_desc: "Receba emails com novidades sobre o editor de níveis e anúncios."
adventurer_introduction: "Vamos ser claros sobre o seu papel: você é o tanque. Você vai tomar dano pesado. Precisamos de pessoas para experimentar níveis inéditos e ajudar a identificar como fazer as coisas melhorarem. A dor será enorme, fazer bons jogos é um processo longo e ninguém acerta na primeira vez. Se você pode suportar e ter uma alta pontuação de constituição, então esta classe pode ser para você."
adventurer_attribute_1: "Sede de aprendizado. Você quer aprender a codificar e nós queremos ensiná-lo a codificar. Você provavelmente vai fazer a maior parte do ensino neste caso."
adventurer_attribute_2: "Carismático. Seja gentil, mas articulado sobre o que precisa melhorar, e ofereça sugestões sobre como melhorar."
adventurer_join_pref: "Se reuna (ou recrute!) um Artesão e trabalhe com ele, ou marque a caixa abaixo para receber emails quando houver novos níveis para testar. Também estaremos postando sobre níveis que precisam de revisão em nossas redes como"
adventurer_forum_url: "nosso fórum"
adventurer_join_suf: "então se você prefere ser notificado dessas formas, inscreva-se lá!"
more_about_adventurer: "Saiba Mais Sobre Como Se Tornar Um Valente Aventureiro"
adventurer_subscribe_desc: "Receba emails quando houver novos níveis para testar."
scribe_introduction_pref: "O CodeCombat não será apenas um monte de níveis. Ele também irá incluir uma fonte de conhecimento, um wiki de conceitos de programação que os níveis podem se basear. Dessa forma, em vez de cada Artesão ter que descrever em detalhes o que é um operador de comparação, eles podem simplesmente criar um link para o artigo que o descreve. Algo na linha do que a "
scribe_introduction_url_mozilla: "Mozilla Developer Network"
scribe_introduction_suf: " construiu. Se a sua idéia de diversão é articular os conceitos de programação em Markdown, então esta classe pode ser para você."
scribe_attribute_1: "Habilidade com palavras é praticamente tudo o que você precisa. Não só a gramática e ortografica, mas a capacidade de transmitir idéias muito complicadas para os outros."
contact_us_url: "Contate-nos"
scribe_join_description: "conte-nos um pouco sobre você, sua experiência com programação e que tipo de coisas você gostaria de escrever sobre. Nós começaremos a partir disso!"
more_about_scribe: "Saiba Mais Sobre Como Se Tornar Um Escriba Aplicado"
scribe_subscribe_desc: "Receba email sobre anúncios de escrita de artigos."
diplomat_introduction_pref: "Então, se há uma coisa que aprendemos com o "
diplomat_launch_url: "lançamento em Outubro"
diplomat_introduction_suf: "é que há um interesse considerável no CodeCombat em outros países, especialmente no Brasil! Estamos construindo um corpo de tradutores ansiosos para transformar um conjunto de palavras em outro conjunto de palavras para tornar o CodeCombat tão acessível em todo o mundo quanto for possível. Se você gosta de obter cenas inéditas do próximo conteúdo e obter esses níveis para os seus compatriotas o mais rápido possível, então esta classe pode ser para você."
diplomat_attribute_1: "Fluência no inglês e na língua para a qual você gostaria de traduzir. Ao transmitir idéias complicadas, é importante ter um forte domínio em ambos!"
diplomat_join_pref: "Nós começamos várias traduções iniciais "
diplomat_doc_url: "nesta publicação no fórum"
diplomat_join_suf: "então confira a publicação e comece a adicionar coisas para o seu idioma. Além disso, marque a caixa abaixo para se manter atualizado sobre os novos desenvolvimentos da internacionalização!"
more_about_diplomat: "Saiba Mais Sobre Como Se Tornar Um Ótimo Diplomata"
diplomat_subscribe_desc: "Receba emails sobre o desenvolvimento da i18n e níveis para traduzir."
ambassador_introduction: "Esta é uma comunidade que estamos construindo, e vocês são as conexões. Temos chats Olark, emails e redes sociais com muitas pessoas para conversar e ajudar a se familiarizar com o jogo e aprender. Se você quer ajudar as pessoas a se envolver e se divertir, e ter uma boa noção da pulsação do CodeCombat e para onde estamos indo em seguida, esta classe pode ser para você."
ambassador_attribute_1: "Habilidades de comunicação. Ser capaz de identificar os problemas que os jogadores estão tendo e ajudar a resolvê-los, Além disso, manter o resto de nós informados sobre o que os jogadores estão dizendo, o que gostam e não gostam e do que querem mais!"
ambassador_join_desc: "conte-nos um pouco sobre você, o que você fez e o que você estaria interessado em fazer. Nós começaremos a partir disso!"
ambassador_join_note_strong: "Nota"
ambassador_join_note_desc: "Uma das nossas principais prioridades é a construção de um multiplayer onde os jogadores que estão com dificuldade para resolver um nível podem invocar feitiçeiros com nível mais alto para ajudá-los. Esta será uma ótima maneira para os embaixadores fazerem suas tarefas. Vamos mantê-lo informado!"
more_about_ambassador: "Saiba Mais Sobre Como Se Tornar Um Embaixador Prestativo"
ambassador_subscribe_desc: "Receba emails sobre atualização do suporte e desenvolvimento do multiplayer."
counselor_introduction_1: "Você tem experiência de vida? Uma perspectiva diferente sobre as coisas podem nos ajudar a decidir como moldar o CodeCombat? De todos os papéis, este provavelmente vai demorar menos tempo, mas individualmente você pode fazer mais diferença. Estamos à procura de sábios, particularmente em áreas como: ensino, desenvolvimento de jogos, gerenciamente de projetos de código aberto, recrutamento técnico, empreendedorismo ou design."
counselor_introduction_2: "Ou realmente tudo o que é relevante para o desenvolvimento do CodeCombat. Se você tem conhecimento e quer compartilhá-lo para ajudar este projeto a crescer, esta classe pode ser para você."
counselor_attribute_1: "Experiência, em qualquer uma das áreas acima ou alguma coisa que você pense ser útil."
counselor_attribute_2: "Um pouco de tempo livre!"
counselor_join_desc: "conte-nos um pouco sobre você, o que você fez e o que você estaria interessado em fazer. Vamos colocá-lo em nossa lista de contatos e entraremos em contato quando precisarmos de um conselho (não muitas vezes)."
more_about_counselor: "Saiba Mais Sobre Como Se Tornar Um Conselheiro Valioso"
changes_auto_save: "As alterações são salvas automaticamente quando você marcar as caixas de seleção."
diligent_scribes: "Nossos Aplicados Escribas:"
powerful_archmages: "Nossos Poderosos Arquimagos:"
creative_artisans: "Nossos Criativos Artesãos:"
brave_adventurers: "Nossos Valentes Aventureiros:"
translating_diplomats: "Nossos Diplomatas Tradutores:"
helpful_ambassadors: "Nossos Prestativos Embaixadores:"
classes:
archmage_title: "Arquimago"
archmage_title_description: "(Codificador)"
artisan_title: "Artesão"
artisan_title_description: "(Construtor de Nível)"
adventurer_title: "Aventureiro"
adventurer_title_description: "(Testador de Nível)"
scribe_title: "Escriba"
scribe_title_description: "(Escritor de Artigos)"
diplomat_title: "Diplomata"
diplomat_title_description: "(Tradutor)"
ambassador_title: "Embaixador"
ambassador_title_description: "(Suporte)"
counselor_title: "Conselheiro"
counselor_title_description: "(Especialista/Professor)"

View file

@ -1,8 +1,8 @@
module.exports = nativeDescription: "简体中文", englishDescription: "Chinese (Simplified)", translation:
common:
loading: "读取中..."
loading: "读取中……"
saving: "保存中……"
sending: "发送中..."
sending: "发送中……"
cancel: "退出"
# save: "Save"
delay_1_sec: "1 秒"
@ -53,33 +53,33 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
signup:
# create_account_title: "Create Account to Save Progress"
description: "这是免费的简单易学:"
description: "这是免费的简单易学:"
email_announcements: "通过邮件接收通知"
coppa: "13 岁以上或非美国用户"
coppa: "13岁以上或非美国用户"
coppa_why: "为什么?"
creating: "账户创建中..."
creating: "账户创建中……"
sign_up: "注册"
log_in: "登录"
home:
slogan: "通过玩儿游戏学到Javascript脚本语言"
no_ie: "抱歉Internet Explorer 9等更旧的预览器打不开此网站"
no_ie: "抱歉Internet Explorer 9等更旧的预览器打不开此网站"
no_mobile: "CodeCombat 不是针对手机设备设计的,所以可能不好用!"
play: ""
play:
choose_your_level: "选取难度"
adventurer_prefix: "你可以选择以下任意关卡,或者讨论以上的关卡 "
adventurer_forum: "冒险论坛"
adventurer_prefix: "你可以选择以下任意关卡,或者讨论以上的关卡"
adventurer_forum: "冒险论坛"
adventurer_suffix: "."
campaign_beginner: "新手作战"
campaign_beginner_description: "...在这里可以学到编程技巧。"
campaign_beginner_description: "……在这里可以学到编程技巧。"
campaign_dev: "随机困难关卡"
campaign_dev_description: "...在这里你可以学到做一些复杂功能的接口。"
campaign_dev_description: "……在这里你可以学到做一些复杂功能的接口。"
campaign_multiplayer: "多人竞技场"
campaign_multiplayer_description: "...在这里你可以和其他玩家们进行代码肉搏战。"
campaign_multiplayer_description: "……在这里你可以和其他玩家们进行代码肉搏战。"
campaign_player_created: "已创建的玩家"
campaign_player_created_description: "...在这里你可以与你的小伙伴的创造力战斗 <a href=\"/contribute#artisan\">技术指导</a>."
campaign_player_created_description: "……在这里你可以与你的小伙伴的创造力战斗 <a href=\"/contribute#artisan\">技术指导</a>."
level_difficulty: "难度"
contact:
@ -90,13 +90,13 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
contribute_suffix: ""
forum_prefix: "对任何公共部分,放手去干吧 "
forum_page: "我们的论坛"
forum_suffix: "代替"
forum_suffix: "代替 "
send: "意见反馈"
diplomat_suggestion:
title: "帮我们翻译CodeCombat"
title: "帮我们翻译 CodeCombat"
sub_heading: "我们需要您的语言技能"
pitch_body: "我们开发了CodeCombat的英文版但是现在我们的玩家遍布全球。很多人想玩中文简体版的却不会说英语所以如果你中英文都会请考虑一下参加我们的翻译工作帮忙把 CodeCombat 网站还有所有的关卡翻译成中文(简体)。"
pitch_body: "我们开发了 CodeCombat 的英文版,但是现在我们的玩家遍布全球。很多人想玩中文(简体)版的,却不会说英语,所以如果你中英文都会,请考虑一下参加我们的翻译工作,帮忙把 CodeCombat 网站还有所有的关卡翻译成中文(简体)。"
missing_translations: "未翻译的文本将显示为英文。"
learn_more: "了解更多有关成为翻译人员的说明"
subscribe_as_diplomat: "提交翻译人员申请"
@ -122,7 +122,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
new_password_verify: "核实"
email_subscriptions: "邮箱验证"
email_announcements: "通知"
# email_notifications_description: "Get periodic notifications for your account."
email_notifications_description: "定期接受来自你的账户的通知。"
email_announcements_description: "接收关于 CodeCombat 最近的新闻和发展的邮件。"
contributor_emails: "贡献者通知"
contribute_prefix: "我们在寻找志同道合的人!请到 "
@ -153,7 +153,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
level_load_error: "关卡不能载入。"
done: "完成"
grid: "格子"
customize_wizard: "自定义巫师"
customize_wizard: "自定义向导"
home: "主页"
guide: "指南"
multiplayer: "多人游戏"
@ -168,7 +168,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
victory_title_suffix: " 完成"
victory_sign_up: "保存进度"
victory_sign_up_poke: "想保存你的代码?创建一个免费账户吧!"
victory_rate_the_level: "评估关卡: "
victory_rate_the_level: "评估关卡"
victory_play_next_level: "下一关"
victory_go_home: "返回主页"
victory_review: "给我们反馈!"
@ -178,7 +178,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
multiplayer_link_description: "把这个链接告诉小伙伴们,一起玩吧。"
multiplayer_hint_label: "提示:"
multiplayer_hint: " 点击全选,然后按 Apple-C苹果电脑或 Ctrl-C 复制链接。"
multiplayer_coming_soon: "多人游戏的更多特性!"
multiplayer_coming_soon: "多人游戏的更多特性"
guide_title: "指南"
tome_minion_spells: "助手的咒语"
tome_read_only_spells: "只读的咒语"
@ -190,7 +190,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
tome_select_spell: "选择一个法术"
tome_select_a_thang: "选择人物来 "
tome_available_spells: "可用的法术"
hud_continue: "继续 (按 shift-空格)"
hud_continue: "继续(按 shift-空格)"
# spell_saved: "Spell Saved"
# admin:

View file

@ -3,14 +3,14 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
loading: "Loading..."
saving: "儲存中..."
sending: "發送中...."
cancel: "退出"
# save: "Save"
cancel: "取消"
save: "存檔"
delay_1_sec: "1 秒"
delay_3_sec: "3 秒"
delay_5_sec: "5 秒"
manual: "手動手动"
# fork: "Fork"
play: ""
manual: "手動發動"
fork: "Fork"
play: "播放" # Play timeline in a level.
modal:
close: "關閉"
@ -20,18 +20,18 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
page_not_found: "找不到網頁"
nav:
play: ""
play: "開始遊戲"
editor: "編輯"
blog: "博客"
blog: "官方部落格" # Official blog
forum: "論壇"
admin: "超級管理員"
admin: "系統管理員"
home: "首頁"
contribute: "貢獻"
legal: "法律"
legal: "版權聲明" # Copyright
about: "關於"
contact: "聯繫我們"
twitter_follow: "關注"
# employers: "Employers"
twitter_follow: "在Twitter關注"
employers: "招募訊息"
# versions:
# save_version_title: "Save New Version"
@ -43,61 +43,61 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
login:
sign_up: "註冊"
log_in: ""
log_out: "退"
recover: "找回帳"
log_in: ""
log_out: ""
recover: "找回帳"
recover:
recover_account_title: "復原帳"
# send_password: "Send Recovery Password"
recover_account_title: "復原帳"
send_password: "送出新密碼"
signup:
# create_account_title: "Create Account to Save Progress"
description: "這是免費的。超簡單的喲"
create_account_title: "建立帳號儲存進度"
description: "登入以儲存遊戲進度"
email_announcements: "通過郵件接收通知"
coppa: "13歲以上或非美國公民"
coppa_why: "爲什麽?"
creating: "戶創建中..."
creating: "號建立中..."
sign_up: "註冊"
log_in: ""
log_in: ""
home:
slogan: "通過玩遊戲學習Javascript 腳本語言"
no_ie: "抱歉Internet Explorer 9 等舊的瀏覽器打不開此網站"
no_mobile: "CodeCombat 不是針對手機設備設計的,所以可能會出問題!"
play: ""
play: "開始遊戲"
play:
choose_your_level: "選取難度"
choose_your_level: "選取關卡"
adventurer_prefix: "你可以選擇以下任意關卡,或者討論以上的關卡 "
adventurer_forum: "冒險家論壇"
adventurer_suffix: "."
campaign_beginner: "新手作戰"
campaign_beginner: "新手指南"
campaign_beginner_description: "...在這裡可以學到編程技巧。"
campaign_dev: "隨機關卡"
campaign_dev_description: "...在這裡你可以學到做一些複雜功能的接口"
campaign_dev_description: "...在這裡你可以學到做一些較複雜的程式技巧"
campaign_multiplayer: "多人競技場"
campaign_multiplayer_description: "...在這裡你可以和其他玩家們進行代碼近戰。"
campaign_player_created: "已創建的玩家"
campaign_player_created_description: "...在這裡你可以與你的小夥伴的創造力戰鬥 <a href=\"/contribute#artisan\">技術指導</a>."
campaign_multiplayer_description: "...在這裡你可以和其他玩家進行對戰。"
campaign_player_created: "玩家建立的關卡"
campaign_player_created_description: "...挑戰同伴的創意 <a href=\"/contribute#artisan\">技術指導</a>."
level_difficulty: "難度"
contact:
contact_us: "聯繫我們"
welcome: "很高興收到你的信!用這個表格給我們發電郵。 "
contribute_prefix: "如果你想貢獻代碼,請看 "
contribute_page: "貢獻代碼頁面"
contribute_prefix: "如果你想貢獻程式,請看 "
contribute_page: "程式貢獻頁面"
contribute_suffix: ""
forum_prefix: "對於任何公共部份,放手去做吧 "
forum_page: "我們的論壇"
forum_suffix: "代替"
forum_prefix: "如果有任何問題, 請至"
forum_page: "論壇"
forum_suffix: "討論"
sending: "發送中。。。"
send: "意見反饋"
diplomat_suggestion:
title: "幫我們翻譯CodeCombat"
sub_heading: "我們需要您的語言技能"
pitch_body: "我們開發了CodeCombat的英文版但是現在我們的玩家遍佈全球。很多人想玩中文(繁体)版的,卻不會說英文,所以如果你中英文都會,請考慮一下參加我們的翻譯工作,幫忙把 CodeCombat 網站還有所有的關卡翻譯成中文(繁体)。"
pitch_body: "我們開發了CodeCombat的英文版但是現在我們的玩家遍佈全球。很多人想玩中文版的,卻不會說英文,所以如果你中英文都會,請考慮一下參加我們的翻譯工作,幫忙把 CodeCombat 網站還有所有的關卡翻譯成中文(繁体)。"
missing_translations: "直至所有正體中文的翻譯完畢,當無法提供正體中文時還會以英文顯示。"
learn_more: "關於成為外交官"
subscribe_as_diplomat: "註冊成為外交官"
@ -107,91 +107,92 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
# customize_avatar: "Customize Your Avatar"
account_settings:
title: "戶設置"
not_logged_in: "登錄或創建一個帳戶來修改設置。"
title: "號設定"
not_logged_in: "登錄或建立一個帳號來修改設置。"
autosave: "自動保存修改"
me_tab: ""
picture_tab: "圖片"
picture_tab: "頭像"
wizard_tab: "巫師"
password_tab: "密碼"
emails_tab: "郵件"
gravatar_select: "選擇使用 Gravatar 照片"
gravatar_add_photos: "添加小圖和照片到一个 Gravatar 帳戶供你選擇。"
gravatar_add_more_photos: "添加更多照片到你的 Gravatar 帳戶并查看。"
gravatar_select: "選擇一個Gravatar"
gravatar_add_photos: "上傳頭像到Gravatar"
# gravatar_add_more_photos: "Add thumbnails and photos to a Gravatar account for your email to choose an image."
wizard_color: "巫師 衣服 顏色"
new_password: "新密碼"
new_password_verify: "核實"
email_subscriptions: "郵箱驗證"
new_password_verify: "確認密碼"
email_subscriptions: "訂閱"
email_announcements: "通知"
email_announcements_description: "接收關於 CodeCombat 最近的新聞和發展的郵件。"
email_notifications_description: "接收帳號通知"
email_announcements_description: "接收關於 CodeCombat 的新聞和開發消息。"
contributor_emails: "貢獻者電郵"
contribute_prefix: "我們在尋找志同道合的人!請到 "
contribute_page: "貢獻頁面"
contribute_suffix: " 查看更多信息。"
email_toggle: "切換所有"
error_saving: "保存時出錯"
saved: "保存修改"
password_mismatch: "密碼不匹配"
email_toggle: "全選"
error_saving: "保存時發生錯誤"
saved: "修改已儲存"
password_mismatch: "密碼不正確"
account_profile:
edit_settings: "編輯設置"
profile_for_prefix: "關於TA的基本資料"
# profile_for_suffix: ""
edit_settings: "帳號設定"
profile_for_prefix: "關於"
profile_for_suffix: "的基本資料"
profile: "基本資料"
user_not_found: "沒有找到用戶。檢查URL"
gravatar_not_found_mine: "我們找不到TA的基本資料"
gravatar_not_found_email_suffix: "."
gravatar_signup_prefix: "去註冊 "
gravatar_signup_suffix: " 去設置!"
gravatar_not_found_other: "哎呦,沒有與這個人的郵箱相關的資料。"
gravatar_contact: "聯繫"
gravatar_not_found_mine: "我們找不到有關"
gravatar_not_found_email_suffix: "的資料"
gravatar_signup_prefix: "請至"
gravatar_signup_suffix: " 註冊帳號"
gravatar_not_found_other: "哎呦,找不到這個地址的資料。"
gravatar_contact: "聯繫我們"
gravatar_websites: "網站"
gravatar_accounts: "顯示為"
gravatar_profile_link: "完善 Gravatar 資料"
play_level:
level_load_error: "關卡不能載入。"
level_load_error: "載入關卡時發生錯誤"
done: "完成"
grid: "格子"
customize_wizard: "自定義巫師"
home: ""
home: ""
guide: "指南"
multiplayer: "多人遊戲"
restart: "重新開始"
goals: "目標"
action_timeline: "行動時間軸"
click_to_select: "點擊選擇一個單元。"
reload_title: "載所有代碼?"
reload_really: "確定重載這一關,返回開始處"
reload_confirm: "載所有"
# victory_title_prefix: ""
reload_title: "新載入程式碼?"
reload_really: "確定重設所有的程式碼"
reload_confirm: "設所有程式碼"
victory_title_prefix: ""
victory_title_suffix: " 完成"
victory_sign_up: "保存進度"
victory_sign_up_poke: "想保存你的代碼?創建一個免費帳戶吧!"
victory_sign_up_poke: "想保存你的程式碼?建立一個免費帳號吧!"
victory_rate_the_level: "評估關卡: "
victory_play_next_level: "下一關"
victory_go_home: "返回"
victory_review: "給我們饋!"
victory_go_home: "返回"
victory_review: "給我們饋!"
victory_hour_of_code_done: "你完成了嗎?"
victory_hour_of_code_done_yes: "是的,我完成了我的碼!"
multiplayer_title: "多人遊戲設"
multiplayer_link_description: "把這個鏈接告訴小夥伴們,一起玩吧。"
victory_hour_of_code_done_yes: "是的,我完成了我的程式碼!"
multiplayer_title: "多人遊戲設"
multiplayer_link_description: "把這個連結告訴同伴們,一起玩吧。"
multiplayer_hint_label: "提示:"
multiplayer_hint: " 點擊全選,然後按 Apple-C苹果電腦或 Ctrl-C 複製鏈接"
multiplayer_coming_soon: "多人遊戲的更多特性!"
multiplayer_hint: " 點擊全選,然後按 ⌘-C 或 Ctrl-C 複製連結"
multiplayer_coming_soon: "請期待更多的多人關卡!"
guide_title: "指南"
tome_minion_spells: "助手的咒語"
tome_read_only_spells: "讀的咒語"
tome_other_units: "其他單"
tome_read_only_spells: "讀的咒語"
tome_other_units: "其他單"
tome_cast_button_castable: "發動"
tome_cast_button_casting: "發動中"
tome_cast_button_cast: "咒語"
tome_autocast_delay: "自動施法延遲"
tome_select_spell: "選擇一個法術"
tome_select_a_thang: "選擇人物來 "
tome_select_a_thang: "選擇一個人物來施放"
tome_available_spells: "可用的法術"
hud_continue: "繼續 (按 shift-空格)"
# spell_saved: "Spell Saved"
spell_saved: "咒語已儲存"
# admin:
# av_title: "Admin Views"
@ -245,33 +246,33 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
# general:
# and: "and"
or: ""
name: "名字"
# or: ""
# name: "名字"
# body: "Body"
# version: "Version"
# commit_msg: "Commit Message"
# version_history_for: "Version History for: "
# results: "Results"
# description: "Description"
email: "郵箱"
message: "留言"
# email: "Email"
# message: "訊息"
# about:
# who_is_codecombat: "Who is CodeCombat?"
# why_codecombat: "Why CodeCombat?"
# who_description_prefix: "together started CodeCombat in 2013. We also created "
# who_description_suffix: "in 2008, growing it to the #1 web and iOS application for learning to write Chinese and Japanese characters."
# who_description_ending: "Now it's time to teach people to write code."
# why_paragraph_1: "When making Skritter, George didn't know how to program and was constantly frustrated by his inability to implement his ideas. Afterwards, he tried learning, but the lessons were too slow. His housemate, wanting to reskill and stop teaching, tried Codecademy, but \"got bored.\" Each week another friend started Codecademy, then dropped off. We realized it was the same problem we'd solved with Skritter: people learning a skill via slow, intensive lessons when what they need is fast, extensive practice. We know how to fix that."
# why_paragraph_2: "Need to learn to code? You don't need lessons. You need to write a lot of code and have a great time doing it."
# why_paragraph_3_prefix: "That's what programming is about. It's gotta be fun. Not fun like"
# why_paragraph_3_italic: "yay a badge"
# why_paragraph_3_center: "but fun like"
# why_paragraph_3_italic_caps: "NO MOM I HAVE TO FINISH THE LEVEL!"
# why_paragraph_3_suffix: "That's why CodeCombat is a multiplayer game, not a gamified lesson course. We won't stop until you can't stop--but this time, that's a good thing."
# why_paragraph_4: "If you're going to get addicted to some game, get addicted to this one and become one of the wizards of the tech age."
# why_ending: "And hey, it's free. "
# why_ending_url: "Start wizarding now!"
about:
who_is_codecombat: "什麼是CodeCombat?"
why_codecombat: "為什麼使用CodeCombat?"
who_description_prefix: "在2013年共同創立了CodeCombat. 在2008年, 我們創立了"
who_description_suffix: "排名第一的中、日文字的學習網頁及iOS系統應用程式。"
who_description_ending: "這次,我們將教大家如何寫程式。"
why_paragraph_1: "當我們在研發Skritter時George不會寫程式所以常常無法展現他的想法。他嘗試去學然而課程成果緩慢。他的室友曾想學習新技能試過Codecademy但厭倦了。每周也都有其他朋友投入Codecademy卻都以失敗告終。我們發現這與我們藉由Skritter所想解決的問題是一致的─人們需要的不是繁瑣又密集的課程, 而是快速而大量的練習。我們知道該如何改善這個情況。"
why_paragraph_2: "想學程式嗎? 你不需要課程。你需要的只是大量的時間去\"\"程式。"
why_paragraph_3_prefix: "寫程式應該是有趣的。當然不是"
why_paragraph_3_italic: "「耶!拿到獎章了。」"
why_paragraph_3_center: "的有趣, 而是"
why_paragraph_3_italic_caps: "「媽我不要出去玩,我要寫完這段!」"
why_paragraph_3_suffix: "般引人入勝。這是為甚麼CodeCombat被設計成多人對戰「遊戲」而不是遊戲化「課程」。在你對這遊戲無法自拔之前我們是不會放棄的─幫然這個遊戲將是有益於你的。"
why_paragraph_4: "如果你要沉迷遊戲的話就來沉迷CodeCombat成為科技時代的魔法師吧"
why_ending: "啊還有,他是免費的。"
why_ending_url: "那還等什麼? 馬上開始!"
# george_description: "CEO, business guy, web designer, game designer, and champion of beginning programmers everywhere."
# scott_description: "Programmer extraordinaire, software architect, kitchen wizard, and master of finances. Scott is the reasonable one."
# nick_description: "Programming wizard, eccentric motivation mage, and upside-down experimenter. Nick can do anything and chooses to build CodeCombat."

View file

@ -25,7 +25,7 @@ class CocoModel extends Backbone.Model
@loadSchema()
@once 'sync', @onLoaded, @
@saveBackup = _.debounce(@saveBackup, 500)
type: ->
@constructor.className
@ -36,18 +36,18 @@ class CocoModel extends Backbone.Model
if @saveBackups
existing = storage.load @id
if existing
@set(existing, {silent:true})
@set(existing, {silent:true})
CocoModel.backedUp[@id] = @
set: ->
res = super(arguments...)
@saveBackup() if @saveBackups and @loaded and @hasLocalChanges()
res
saveBackup: ->
storage.save(@id, @attributes)
CocoModel.backedUp[@id] = @
@backedUp = {}
loadSchema: ->
@ -55,7 +55,7 @@ class CocoModel extends Backbone.Model
@constructor.schema = new CocoSchema(@urlRoot)
@constructor.schema.fetch()
@constructor.schema.on 'sync', =>
@constructor.schema.once 'sync', =>
@constructor.schema.loaded = true
@addSchemaDefaults()
@trigger 'schema-loaded'
@ -81,7 +81,7 @@ class CocoModel extends Backbone.Model
return super attrs, options
fetch: ->
super()
super(arguments...)
@loading = true
markToRevert: ->
@ -90,7 +90,7 @@ class CocoModel extends Backbone.Model
revert: ->
@set(@_revertAttributes, {silent: true}) if @_revertAttributes
@clearBackup()
clearBackup: ->
storage.remove @id
@ -127,7 +127,7 @@ class CocoModel extends Backbone.Model
#console.log "setting", prop, "to", sch.default, "from sch.default" if sch.default?
@set prop, sch.default if sch.default?
getReferencedModels: (data, schema, path='/') ->
getReferencedModels: (data, schema, path='/', shouldLoadProjection=null) ->
# returns unfetched model shells for every referenced doc in this model
# OPTIMIZE so that when loading models, it doesn't cause the site to stutter
data ?= @attributes
@ -136,18 +136,18 @@ class CocoModel extends Backbone.Model
if $.isArray(data) and schema.items?
for subData, i in data
models = models.concat(@getReferencedModels(subData, schema.items, path+i+'/'))
models = models.concat(@getReferencedModels(subData, schema.items, path+i+'/', shouldLoadProjection))
if $.isPlainObject(data) and schema.properties?
for key, subData of data
continue unless schema.properties[key]
models = models.concat(@getReferencedModels(subData, schema.properties[key], path+key+'/'))
models = models.concat(@getReferencedModels(subData, schema.properties[key], path+key+'/', shouldLoadProjection))
model = CocoModel.getReferencedModel data, schema
model = CocoModel.getReferencedModel data, schema, shouldLoadProjection
models.push model if model
return models
@getReferencedModel: (data, schema) ->
@getReferencedModel: (data, schema, shouldLoadProjection=null) ->
return null unless schema.links?
linkObject = _.find schema.links, rel: "db"
return null unless linkObject
@ -158,9 +158,9 @@ class CocoModel extends Backbone.Model
link = link.replace('{(original)}', data.original)
link = link.replace('{(majorVersion)}', '' + (data.majorVersion ? 0))
link = link.replace('{($)}', data)
@getOrMakeModelFromLink(link)
@getOrMakeModelFromLink(link, shouldLoadProjection)
@getOrMakeModelFromLink: (link) ->
@getOrMakeModelFromLink: (link, shouldLoadProjection=null) ->
makeUrlFunc = (url) -> -> url
modelUrl = link.split('/')[2]
modelModule = _.string.classify(modelUrl)
@ -175,6 +175,9 @@ class CocoModel extends Backbone.Model
return
model = new Model()
if shouldLoadProjection? model
sep = if link.search(/\?/) is -1 then "?" else "&"
link += sep + "project=true"
model.url = makeUrlFunc(link)
return model

View file

@ -106,13 +106,13 @@ module.exports = class Level extends CocoModel
height = c.height if c.height? and c.height > height
return {width:width, height:height}
getReferencedModels: (data, schema, path='/') ->
models = super data, schema, path
getReferencedModels: (data, schema, path='/', shouldLoadProjection=null) ->
models = super data, schema, path, shouldLoadProjection
if path.match(/\/systems\/\d+\/config\//) and data?.indieSprites?.length
# Ugh, we need to make sure we grab the IndieSprite ThangTypes
for indieSprite in data.indieSprites
link = "/db/thang_type/#{indieSprite.thangType}/version"
model = CocoModel.getOrMakeModelFromLink link
model = CocoModel.getOrMakeModelFromLink link, shouldLoadProjection
models.push model if model
else if path is '/'
# We also we need to make sure we grab the Wizard ThangType and the Marks. Hackitrooooid!
@ -124,6 +124,6 @@ module.exports = class Level extends CocoModel
["Repair", "52bcc4591f766a891c000003"]
]
link = "/db/thang_type/#{original}/version"
model = CocoModel.getOrMakeModelFromLink link
model = CocoModel.getOrMakeModelFromLink link, shouldLoadProjection
models.push model if model
models

View file

@ -13,7 +13,7 @@ module.exports = class LevelComponent extends CocoModel
attrs.js = @compile attrs.code
super attrs, options
onLoaded: =>
onLoaded: ->
super()
@set 'js', @compile(@get 'code') unless @get 'js'

View file

@ -13,7 +13,7 @@ module.exports = class LevelSystem extends CocoModel
attrs.js = @compile attrs.code
super attrs, options
onLoaded: =>
onLoaded: ->
super()
@set 'js', @compile(@get 'code') unless @get 'js'

View file

@ -8,35 +8,43 @@ class SuperModel
@mustPopulate = model
model.saveBackups = @shouldSaveBackups(model)
model.fetch() unless model.loaded or model.loading
model.on('sync', @modelLoaded) unless model.loaded
model.once('error', @modelErrored) unless model.loaded
model.once('sync', @modelLoaded, @) unless model.loaded
model.once('error', @modelErrored, @) unless model.loaded
url = model.url()
@models[url] = model unless @models[url]?
@modelLoaded(model) if model.loaded
# replace or overwrite
shouldPopulate: (url) -> return true
shouldSaveBackups: (model) -> return false
shouldLoadReference: (model) -> true
shouldLoadProjection: (model) -> false
shouldPopulate: (url) -> true
shouldSaveBackups: (model) -> false
modelErrored: (model) =>
modelErrored: (model) ->
@trigger 'error'
@removeEventsFromModel(model)
modelLoaded: (model) =>
modelLoaded: (model) ->
schema = model.schema()
return schema.on('sync', => @modelLoaded(model)) unless schema.loaded
refs = model.getReferencedModels(model.attributes, schema.attributes)
return schema.once('sync', => @modelLoaded(model)) unless schema.loaded
refs = model.getReferencedModels(model.attributes, schema.attributes, '/', @shouldLoadProjection)
refs = [] unless @mustPopulate is model or @shouldPopulate(model)
# console.log 'Loaded', model.get('name')
for ref, i in refs
for ref, i in refs when @shouldLoadReference ref
ref.saveBackups = @shouldSaveBackups(ref)
refURL = ref.url()
continue if @models[refURL]
@models[refURL] = ref
ref.fetch()
ref.on 'sync', @modelLoaded
ref.once 'sync', @modelLoaded, @
@trigger 'loaded-one', model: model
@trigger 'loaded-all' if @finished()
@removeEventsFromModel(model)
removeEventsFromModel: (model) ->
model.off 'sync', @modelLoaded, @
model.off 'error', @modelErrored, @
getModel: (ModelClass_or_url, id) ->
return @getModelByURL(ModelClass_or_url) if _.isString(ModelClass_or_url)

View file

@ -28,7 +28,6 @@ module.exports = class User extends CocoModel
profileUrl = "#{GRAVATAR_URL}#{emailHash}.json?callback=#{functionName}"
script = $("<script src='#{profileUrl}' type='text/javascript'></script>")
$('head').append(script)
$('body').on('load',(e)->console.log('we did it!', e))
window[functionName] = (profile) =>
@gravatarProfile = profile
@trigger('change', @)

View file

@ -1,6 +1,10 @@
@import "bootstrap/variables"
@import "bootstrap/mixins"
// https://github.com/twbs/bootstrap/issues/9237 -- need a version that's not !important
.secret
display: none
h1 h2 h3 h4
letter-spacing: 2px

View file

@ -35,7 +35,7 @@
position: relative
top: 3px
#marker-button
#marker-button, #end-button
float: right
margin-right: 10px
position: relative

View file

@ -1,2 +1,4 @@
#ladder-view
color: black
color: black
.score-cell
width: 50px

View file

@ -56,7 +56,7 @@
input, textarea
position: absolute
bottom: -10px
bottom: 0px
left: 20px
right: 0px
width: 280px

View file

@ -0,0 +1,44 @@
@import "app/styles/mixins"
#gold-view
position: absolute
right: 46%
top: 42px
user-select: none
-webkit-user-select: none
h3
font-size: 16px
margin: 0
line-height: 20px
color: hsla(205,0%,31%,1)
text-shadow: 0px 1px 1px white, 0px -1px 1px white, 1px 0px 1px white, -1px 0px 1px white
&.team-humans
color: hsla(4,80%,51%,1)
&.team-ogres
color: hsla(205,100%,31%,1)
&.team-allies, &.team-minions
color: hsla(116,80%,31%,1)
img
width: 16px
height: 16px
border-radius: 2px
padding: 2px
@include gradient-radial-custom-stops(hsla(205,0%,74%,1), 20%, hsla(205,0%,31%,1), 70%)
&.team-humans img
@include gradient-radial-custom-stops(hsla(4,80%,74%,1), 20%, hsla(4,80%,51%,1), 70%)
&.team-ogres img
@include gradient-radial-custom-stops(hsla(205,100%,74%,1), 20%, hsla(205,100%,31%,1), 70%)
&.team-allies img, &.team-minions img
@include gradient-radial-custom-stops(hsla(116,80%,74%,1), 20%, hsla(116,80%,31%,1), 70%)
.gold-amount
display: inline-block
width: 20px

View file

@ -89,10 +89,13 @@
margin: 8px 8px 0 0
overflow-y: scroll
float: left
@include user-select(text)
.prop
img
margin-right: 5px
width: 16px
height: 16px
.text-prop
width: 50%

View file

@ -6,12 +6,10 @@
left: 10px
right: 10px
height: 140px
padding-top: 50px
// Height and padding-top relate to .tab-content height
padding-left: 35px
padding-right: 60px
// Docs' popovers flicker when too far right, so we have big padding-right
// twilight: color: #E2E2E2
// Height relates to .tab-content height
padding-top: 35px
padding-left: 12px
padding-right: 4px
color: #333
// Get crazy with the backgrounds so that we can lower the opacity on the editor background above it, making a gradient of the disabled background color on the top around where it's usually covered
background-color: transparent
@ -36,3 +34,23 @@
line-height: 16px
margin: 0 4px
font-weight: normal
.nav > li > a
padding: 2px 20px 0px 20px
margin-bottom: 3px
ul.nav.nav-pills
li.active a
background-color: transparent
&.multiple-tabs li.active a
background-color: lighten(rgb(230, 212, 146), 10%)
&.multiple-tabs li:not(.active) a
cursor: pointer
//.nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus
// background-color: lighten(rgb(230, 212, 146), 10%)
.property-entry-column
display: inline-block
margin-right: 3px
vertical-align: top

View file

@ -1,28 +1,32 @@
@import "../../../bootstrap/mixins"
@import "app/styles/bootstrap/mixins"
@import "app/styles/mixins"
.spell-palette-entry-view
display: inline-block
margin: 0px 4px
display: block
padding: 0px 4px
font-family: Menlo, Monaco, Consolas, "Courier New", monospace
font-size: 12px
border: 1px solid transparent
cursor: pointer
@include user-select(all)
&:hover
border: 1px solid #BFF
&.pinned
background-color: darken(#BFF, 20%)
// Pulling these colors from the most relevant textmate-theme classes
&.function
// twilight: color: #7587A6
color: #0000A2
&.object
// twilight: color: #AC885B
color: rgb(6, 150, 14)
&.string
// twilight: color: #CDA869
color: rgb(3, 106, 7)
&.number
// twilight: color: #F9EE98
color: rgb(0, 0, 205)
&.boolean
// twilight: color: #C9CEA8
color: rgb(88, 92, 246)
&.undefined
// twilight: color: #CF6A4C
color: rgb(197, 6, 11)

View file

@ -1,11 +1,23 @@
@import "../../../bootstrap/mixins"
@import "app/styles/bootstrap/mixins"
@import "app/styles/mixins"
#tome-view
height: 100%
> .popover
// Only those popovers which are our direct children (spell documentation)
left: auto !important
top: auto !important
right: 100%
bottom: 151px
@include user-select(text)
&.pinned
@include box-shadow(0 0 500px white)
.popover
padding: 10px
max-width: 400px
min-width: 400px
background: transparent url(/images/level/popover_background.png)
background-size: 100% 100%
border: 0
@ -33,3 +45,6 @@
&.right .arrow
left: -3%
pre
display: inline-block
padding: 5px

View file

@ -8,7 +8,7 @@ block content
p(data-i18n="account_settings.not_logged_in") Log in or create an account to change your settings.
else
button.btn#save-button.disabled.hide(data-i18n="account_settings.saveBackups") Changes Save Automatically
button.btn#save-button.disabled.secret(data-i18n="account_settings.saveBackups") Changes Save Automatically
ul.nav.nav-pills#settings-tabs
li

View file

@ -9,9 +9,9 @@ block content
button.btn.btn-warning#unsubscribe-button(data-i18n="account.unsubscribe_button") Do it
.progress.progress-striped.active.hide
.progress.progress-striped.active.secret
.progress-bar
p.hide#fail-alert(data-i18n="account.unsubscribe_failed").alert.alert-danger Failed
p.secret#fail-alert(data-i18n="account.unsubscribe_failed").alert.alert-danger Failed
p.hide#success-alert(data-i18n="account.unsubscribe_success").alert.alert-success Success
p.secret#success-alert(data-i18n="account.unsubscribe_success").alert.alert-success Success

View file

@ -57,7 +57,7 @@ block content
| create an account
span
span(data-i18n="contribute.alert_account_message_suf")
| first.
| first.
label.checkbox(for="translator").well
input(type='checkbox', name="translator", id="translator")

View file

@ -3,13 +3,13 @@ div.well
span#thang-props
a#thang-name-link
span= thang.id
input.hide(value=thang.id)
input.secret(value=thang.id)
| (
span(data-i18n="editor.level_components_type") Type
| :
a#thang-type-link
span= thang.thangType
input.hide(value=thang.thangType)
input.secret(value=thang.thangType)
| )
#thang-components-edit-view

View file

@ -31,4 +31,4 @@
- path = '/file/db/thang.type/'+thangType.original+'/portrait.png'
img(title="Add " + thangType.name, src=path, alt="")
div.clearfix
#editor-level-thang-edit.hide
#editor-level-thang-edit.secret

View file

@ -1,6 +1,6 @@
div#color-groups-treema
div#color-group-settings.hide
div#color-group-settings.secret
div#shape-buttons
canvas#tinting-display(width=400, height=400)

View file

@ -39,6 +39,9 @@ block content
button.btn.btn-small.btn-primary#marker-button
i.icon-map-marker
button.btn.btn-small.btn-primary#end-button
i.icon-stop
div.slider-cell
| Rotation:
span.rotation-label

View file

@ -25,4 +25,6 @@ block content
div.homepage_button
a#beginner-campaign(href="/play/level/rescue-mission")
canvas(width="125", height="150")
button(data-i18n="home.play").btn.btn-warning.btn-lg.highlight Play
button(data-i18n="home.play").btn.btn-warning.btn-lg.highlight Play
if me.isAdmin()
button.btn.btn-warning.btn-lg.highlight#simulate-button SIMULATE

View file

@ -21,7 +21,7 @@ block content
.modal-footer
button.btn(data-dismiss="modal") Cancel
button.btn.btn-primary.new-model-submit Create
.modal-body.wait.hide
.modal-body.wait.secret
h3 Reticulating Splines...
.progress.progress-striped.active
.progress-bar

View file

@ -22,6 +22,6 @@ block modal-body-content
textarea#contact-message.form-control(name="message", rows=8)
block modal-footer-content
span.sending-indicator.pull-left.hide(data-i18n="common.sending") Sending...
span.sending-indicator.pull-left.secret(data-i18n="common.sending") Sending...
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="common.cancel").btn Cancel
button.btn.btn-primary#contact-submit-button(data-i18n="contact.send") Send Feedback

View file

@ -14,7 +14,7 @@
img(src="http://www.manbitesgod.com/images/picturecoupleb.jpg")
img(src="http://www.manbitesgod.com/images/manrantb.jpg")
.modal-body.wait.hide
.modal-body.wait.secret
block modal-body-wait-content
h3 Reticulating Splines...
.progress.progress-striped.active

View file

@ -5,7 +5,9 @@ block content
h3= level.get('name')
div#level-description
!{description}
if me.isAdmin()
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px") Simulate all games
div#leaderboard-column
ul.nav.nav-pills
for team in teams
@ -19,13 +21,18 @@ block content
h4 Leaderboard
table.table
for session in team.leaderboard.topPlayers.models
tr= session.get('creatorName')
.challengers
h4 Challengers
if team.easyChallenger
div.challenger-select
| Easy challenger:
button
a(href=link+'?team='+team.id+'&opponent='+team.easyChallenger.id)
//TODO: finish once the scoring system is finished
tr
td.score-cell= session.get('totalScore')
td= session.get('creatorName')
td
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") COMPETE
//.challengers
// h4 Challengers
//
// if team.easyChallenger
// div.challenger-select
// | Easy challenger:
// button
// a(href=link+'?team='+team.id+'&opponent='+team.easyChallenger.id)
// TODO: finish once the scoring system is finished

View file

@ -11,10 +11,12 @@
#canvas-left-gradient.gradient
#canvas-top-gradient.gradient
a.btn.btn-primary.banner.hide#level-done-button(data-i18n="play_level.done") Done
a.btn.btn-primary.banner.secret#level-done-button(data-i18n="play_level.done") Done
#goals-view.hide
#goals-view.secret
#gold-view.secret.expanded
#level-chat-view
#playback-view

View file

@ -2,7 +2,7 @@
table
tbody
.open-chat-area.hide
.open-chat-area.secret
table
tbody

View file

View file

@ -25,14 +25,11 @@
p(data-i18n="play_level.multiplayer_coming_soon") More multiplayer features to come!
if playableTeams.length > 1
#multiplayer-team-selection
for team in playableTeams
h4
if team == me.team
em= "Playing as " + team
else
a(href=joinLink + "&skip_protect_api=true&team=" + team)= "Play as " + team
if ladderGame
if me.get('anonymous')
p Sign in or create an account and get your solution on the leaderboard!
else
#submit-session-button.btn.btn-primary Update Ladder Score
.modal-footer
a(href='#', data-dismiss="modal", aria-hidden="true", data-i18n="modal.close").btn.btn-primary Close

View file

@ -15,7 +15,7 @@
button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="modal/signup", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
span(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
else
div.rating.hide
div.rating.secret
span(data-i18n="play_level.victory_rate_the_level") Rate the level:
i.icon-star-empty
i.icon-star-empty
@ -30,7 +30,7 @@
else
a.btn.btn-primary(href="/", data-dismiss="modal", data-i18n="play_level.victory_go_home") Go Home
if !me.get('anonymous')
div.review.hide
div.review.secret
span(data-i18n="play_level.victory_review") Tell us more!
br
textarea

View file

@ -11,7 +11,7 @@ button.btn.btn-xs.btn-inverse#music-button(title="Toggle Music")
| ♫
.scrubber
.progress.hide
.progress.secret
.progress-bar
.scrubber-handle
@ -27,11 +27,11 @@ button.btn.btn-xs.btn-inverse#music-button(title="Toggle Music")
li(title="Ctrl/Cmd + \\: Toggle debug display").selectable#debug-toggle
i.icon-globe
| Debug Mode
i.icon-ok.hide
i.icon-ok.secret
li(title="Ctrl/Cmd + G: Toggle grid display").selectable#grid-toggle
i.icon-th
span(data-i18n="play_level.grid") Grid
i.icon-ok.hide
i.icon-ok.secret
li.selectable#edit-wizard-settings
i.icon-user
span(data-i18n="play_level.customize_wizard") Customize Wizard

View file

@ -1,5 +1,5 @@
button.close(type="button", data-dismiss="alert") &times;
span.problem-message= message
span.problem-message!= message
if hint
br
span.problem-hint= hint
span.problem-hint!= hint

View file

@ -1,3 +1,10 @@
img(src="/images/level/code_palette_background.png").code-palette-background
h4(data-i18n="play_level.tome_available_spells") Available Spells
.properties
ul(class="nav nav-pills" + (tabbed ? ' multiple-tabs' : ''))
each slug, group in entryGroupSlugs
li(class=group == "this" || slug == "available-spells" ? "active" : "")
a(data-toggle="pill", data-target='#palette-tab-' + slug)
h4= group
.tab-content
each slug, group in entryGroupSlugs
div(id="palette-tab-" + slug, class="tab-pane" + (group == "this" || slug == "available-spells" ? " active" : ""))
div(class="properties properties-" + slug)

View file

@ -1 +1 @@
// Later we'll put the crazy HTML from docs.coffee in here as jade
span= doc.title

View file

@ -0,0 +1,44 @@
h4
span= doc.shortName
| -
code.prop-type= doc.type == 'function' && doc.owner == 'this' ? 'method' : doc.type
if doc.type != 'function'
| (read-only)
.description!= marked(doc.description || 'Still undocumented, sorry.')
if doc.example
p.example
strong Example:
div!= marked("```\n" + doc.example + "```")
else if doc.type == 'function'
p.example
strong Example:
div
code= doc.owner + '.' + doc.name + '(' + argumentExamples.join(', ') + ');'
if (doc.type != 'function' && doc.type != 'snippet') || doc.name == 'now'
p.value
strong Current Value:
pre
code.current-value(data-prop=doc.name)= value
if doc.args && doc.args.length
p.args
strong Parameters:
for arg in doc.args
div
code= arg.name
| :
code= arg.type
if arg.example
| (ex:
code= arg.example
| )
if arg.description
div!= marked(arg.description)
if arg.default
div
em Default value:
code= arg.default

View file

@ -0,0 +1,16 @@
.level-content
#control-bar-view
#canvas-wrapper
canvas(width=924, height=589)#surface
#canvas-left-gradient.gradient
#canvas-top-gradient.gradient
#goals-view.hide
#gold-view.hide.expanded
#level-chat-view
#playback-view
#thang-hud
.footer
.content
p(class='footer-link-text')
a(title='Contact', tabindex=-1, data-toggle="coco-modal", data-target="modal/contact", data-i18n="nav.contact") Contact

View file

@ -284,7 +284,7 @@ class LatestVersionReferenceNode extends TreemaNode
@lastTerm = term
@getSearchResultsEl().empty().append('Searching')
@collection = new LatestVersionCollection()
@collection.url = @url+'?term='+term
@collection.url = "#{@url}?term=#{term}&project=true"
@collection.fetch()
@collection.on 'sync', @searchCallback

View file

@ -48,7 +48,7 @@ module.exports = class SettingsView extends View
@updateWizardColor()
wizardSettingsTabView = new WizardSettingsTabView()
wizardSettingsTabView.on 'change', @save, @
@insertSubView wizardSettingsTabView
@insertSubView wizardSettingsTabView
chooseTab: (category) ->
id = "##{category}-pane"
@ -102,7 +102,7 @@ module.exports = class SettingsView extends View
res = me.save()
return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.addClass('btn-info').removeClass('hide').removeClass('btn-danger')
.addClass('btn-info').show().removeClass('btn-danger')
res.error ->
errors = JSON.parse(res.responseText)

View file

@ -5,7 +5,7 @@ template = require 'templates/account/unsubscribe'
module.exports = class UnsubscribeView extends RootView
id: "unsubscribe-view"
template: template
events:
'click #unsubscribe-button': 'onUnsubscribeButtonClicked'
@ -15,21 +15,21 @@ module.exports = class UnsubscribeView extends RootView
context
onUnsubscribeButtonClicked: ->
@$el.find('#unsubscribe-button').addClass 'hide'
@$el.find('.progress').removeClass 'hide'
@$el.find('.alert').addClass 'hide'
@$el.find('#unsubscribe-button').hide()
@$el.find('.progress').show()
@$el.find('.alert').hide()
email = @getQueryVariable 'email'
url = "/auth/unsubscribe?email=#{encodeURIComponent(email)}"
success = =>
@$el.find('.progress').addClass 'hide'
@$el.find('#success-alert').removeClass 'hide'
@$el.find('.progress').hide()
@$el.find('#success-alert').show()
me.fetch()
error = =>
@$el.find('.progress').addClass 'hide'
@$el.find('#fail-alert').removeClass 'hide'
@$el.find('#unsubscribe-button').removeClass 'hide'
@$el.find('.progress').hide()
@$el.find('#fail-alert').show()
@$el.find('#unsubscribe-button').show()
$.ajax { url: url, success: success, error: error }

View file

@ -7,7 +7,7 @@ SpriteBuilder = require 'lib/sprites/SpriteBuilder'
module.exports = class WizardSettingsTabView extends RootView
id: 'wizard-settings-tab-view'
template: template
events:
'change .color-group-checkbox': (e) ->
colorGroup = $(e.target).closest('.color-group')
@ -17,13 +17,13 @@ module.exports = class WizardSettingsTabView extends RootView
constructor: ->
super(arguments...)
@loadWizard()
loadWizard: ->
@wizardThangType = new ThangType()
@wizardThangType.url = -> '/db/thang_type/wizard'
@wizardThangType.fetch()
@wizardThangType.once 'sync', @initCanvas, @
initCanvas: ->
@render()
@spriteBuilder = new SpriteBuilder(@wizardThangType)
@ -32,9 +32,9 @@ module.exports = class WizardSettingsTabView extends RootView
getRenderData: ->
c = super()
wizardSettings = me.get('wizard')?.colorConfig or {}
colorGroups = @wizardThangType.get('colorGroups') or {}
f = (name) -> {
f = (name) -> {
dasherized: _.string.dasherize(name)
humanized: _.string.humanize name
name: name
@ -42,26 +42,26 @@ module.exports = class WizardSettingsTabView extends RootView
}
c.colorGroups = (f(colorName) for colorName in _.keys colorGroups)
c
afterRender: ->
wizardSettings = me.get('wizard') or {}
wizardSettings.colorConfig ?= {}
@$el.find('.selector').each (i, slider) =>
[groupName, prop] = $(slider).attr('name').split('.')
value = 100 * (wizardSettings.colorConfig[groupName]?[prop] ? 0.5)
@initSlider $(slider), value, @onSliderChanged
@$el.find('.color-group').each (i, colorGroup) =>
@updateSliderVisibility($(colorGroup))
updateSliderVisibility: (colorGroup) ->
enabled = colorGroup.find('.color-group-checkbox').prop('checked')
colorGroup.find('.sliders').toggleClass 'hide', not enabled
colorGroup.find('.sliders').toggle Boolean(enabled)
updateColorSettings: (colorGroup) ->
wizardSettings = me.get('wizard') or {}
wizardSettings.colorConfig ?= {}
wizardSettings.colorConfig ?= {}
colorName = colorGroup.data('name')
wizardSettings.colorConfig[colorName] ?= {}
if colorGroup.find('.color-group-checkbox').prop('checked')
@ -75,7 +75,7 @@ module.exports = class WizardSettingsTabView extends RootView
me.set('wizard', wizardSettings)
@updateMovieClip()
@trigger 'change'
onSliderChanged: (e, result) =>
@updateColorSettings $(result.handle).closest('.color-group')
@ -84,12 +84,12 @@ module.exports = class WizardSettingsTabView extends RootView
@updateMovieClip()
createjs.Ticker.setFPS 20
createjs.Ticker.addEventListener("tick", @stage)
updateMovieClip: ->
return unless @wizardThangType.loaded
wizardSettings = me.get('wizard') or {}
wizardSettings.colorConfig ?= {}
@stage.removeChild(@movieClip) if @movieClip
options = {colorConfig: wizardSettings.colorConfig}
@spriteBuilder.setOptions options
@ -100,10 +100,10 @@ module.exports = class WizardSettingsTabView extends RootView
@movieClip.scaleY = @movieClip.scaleX = 1.7 * (castAction.scale or 1)
reg = castAction.positions?.registration
if reg
@movieClip.regX = reg.x
@movieClip.regX = reg.x
@movieClip.regY = reg.y
@stage.addChild @movieClip
@stage.update()
destroy: ->
@stage?.removeAllEventListeners()
@stage?.removeAllEventListeners()

View file

@ -52,7 +52,7 @@ module.exports = class ArticleEditView extends View
b.find('#insert').html(m)
b.find('#title').text(@treema.data.name)
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.article = @article
context

View file

@ -20,7 +20,7 @@ module.exports = class ComponentConfigView extends CocoView
@editing = options.editing
@callback = options.callback
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.component = @component
context.configProperties = []

View file

@ -16,7 +16,7 @@ module.exports = class LevelComponentEditView extends View
@levelComponent = @supermodel.getModelByOriginalAndMajorVersion LevelComponent, options.original, options.majorVersion or 0
console.log "Couldn't get levelComponent for", options, "from", @supermodel.models unless @levelComponent
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.editTitle = "#{@levelComponent.get('system')}.#{@levelComponent.get('name')}"
context
@ -89,5 +89,5 @@ module.exports = class LevelComponentEditView extends View
null
destroy: ->
super()
@editor?.destroy()
super()

View file

@ -60,7 +60,7 @@ module.exports = class EditorLevelView extends View
initWorld: ->
@world = new World @level.name
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.level = @level
context

View file

@ -17,7 +17,7 @@ module.exports = class LevelForkView extends View
super options
@level = options.level
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.level = @level
context

View file

@ -16,8 +16,8 @@ module.exports = class LevelSaveView extends SaveVersionModal
constructor: (options) ->
super options
@level = options.level
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.level = @level
context.levelNeedsSave = @level.hasLocalChanges()
@ -63,8 +63,8 @@ module.exports = class LevelSaveView extends SaveVersionModal
console.log "Got errors:", JSON.parse(res.responseText)
forms.applyErrorsToForm($(form), JSON.parse(res.responseText))
res.success =>
@hide()
modelsToSave = _.without modelsToSave, newModel
unless modelsToSave.length
url = "/editor/level/#{@level.get('slug') or @level.id}"
document.location.href = url
@hide() # This will destroy everything, so do it last

View file

@ -81,9 +81,7 @@ module.exports = class ScriptsTabView extends View
@selectedScriptPath = newPath
getThangIDs: ->
ids = (t.id for t in @level.get('thangs') when t.id isnt 'Interface')
ids = ['My Wizard', 'Captain Anya'].concat(ids)
ids
(t.id for t in @level.get('thangs') when t.id isnt 'Interface')
onScriptChanged: =>
@scriptsTreema.set(@selectedScriptPath, @scriptTreema.data)

View file

@ -2,12 +2,13 @@ View = require 'views/kinds/CocoView'
template = require 'templates/editor/level/settings_tab'
Level = require 'models/Level'
Surface = require 'lib/surface/Surface'
nodes = require './treema_nodes'
module.exports = class SettingsTabView extends View
id: 'editor-level-settings-tab-view'
className: 'tab-pane'
template: template
editableSettings: ['name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon'] # not thangs or scripts or the backend stuff
editableSettings: ['name', 'description', 'documentation', 'nextLevel', 'background', 'victory', 'i18n', 'icon', 'goals'] # not thangs or scripts or the backend stuff
subscriptions:
'level-loaded': 'onLevelLoaded'
@ -22,16 +23,24 @@ module.exports = class SettingsTabView extends View
schema = _.cloneDeep Level.schema.attributes
schema.properties = _.pick schema.properties, (value, key) => key in @editableSettings
schema.required = _.intersection schema.required, @editableSettings
thangIDs = @getThangIDs()
treemaOptions =
filePath: "db/level/#{@level.get('original')}"
supermodel: @supermodel
schema: schema
data: data
callbacks: {change: @onSettingsChanged}
thangIDs: thangIDs
nodeClasses:
thang: nodes.ThangNode
@settingsTreema = @$el.find('#settings-treema').treema treemaOptions
@settingsTreema.build()
@settingsTreema.open()
getThangIDs: ->
(t.id for t in @level.get('thangs') when t.id isnt 'Interface')
onSettingsChanged: (e) =>
$('.level-title').text @settingsTreema.data.name
for key in @editableSettings

View file

@ -16,7 +16,7 @@ module.exports = class LevelSystemEditView extends View
@levelSystem = @supermodel.getModelByOriginalAndMajorVersion LevelSystem, options.original, options.majorVersion or 0
console.log "Couldn't get levelSystem for", options, "from", @supermodel.models unless @levelSystem
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.editTitle = "#{@levelSystem.get('name')}"
context
@ -89,5 +89,5 @@ module.exports = class LevelSystemEditView extends View
null
destroy: ->
super()
@editor?.destroy()
super()

View file

@ -9,10 +9,10 @@ module.exports = class LevelThangEditView extends View
Everything below is part of the ThangComponentEditView, which is shared with the
ThangType editor view.
###
id: "editor-level-thang-edit"
template: template
events:
'click #all-thangs-link': 'navigateToAllThangs'
'click #thang-name-link span': 'toggleNameEdit'
@ -28,11 +28,11 @@ module.exports = class LevelThangEditView extends View
@level = options.level
@oldID = @thangData.id
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.thang = @thangData
context
afterRender: ->
options =
components: @thangData.components
@ -40,7 +40,7 @@ module.exports = class LevelThangEditView extends View
level: @level
world: @world
callback: @onComponentsChanged
@thangComponentEditView = new ThangComponentEditView options
@insertSubView @thangComponentEditView
thangTypeNames = (m.get('name') for m in @supermodel.getModels ThangType)
@ -57,35 +57,35 @@ module.exports = class LevelThangEditView extends View
thangData: @thangData
id: @oldID
Backbone.Mediator.publish 'level-thang-edited', event
navigateToAllThangs: ->
Backbone.Mediator.publish 'level-thang-done-editing'
toggleNameEdit: ->
link = @$el.find '#thang-name-link'
wasEditing = link.find('input:visible').length
span = link.find('span')
input = link.find('input')
if wasEditing then span.text(input.val()) else input.val(span.text())
link.find('span, input').toggleClass('hide')
link.find('span, input').toggle()
input.select() unless wasEditing
@thangData.id = span.text()
@saveThang()
toggleTypeEdit: ->
link = @$el.find '#thang-type-link'
wasEditing = link.find('input:visible').length
span = link.find('span')
input = link.find('input')
span.text(input.val()) if wasEditing
link.find('span, input').toggleClass('hide')
span.text(input.val()) if wasEditing
link.find('span, input').toggle()
input.select() unless wasEditing
thangTypeName = input.val()
thangType = _.find @supermodel.getModels(ThangType), (m) -> m.get('name') is thangTypeName
if thangType and wasEditing
@thangData.thangType = thangType.get('original')
@saveThang()
onComponentsChanged: (components) =>
@thangData.components = components
@saveThang()

View file

@ -72,7 +72,7 @@ module.exports = class ThangsTabView extends View
@render() # do it again but without the loading screen
@onLevelLoaded level: @level if @level
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
thangTypes = (thangType.attributes for thangType in @supermodel.getModels(ThangType))
thangTypes = _.uniq thangTypes, false, 'original'
@ -151,9 +151,9 @@ module.exports = class ThangsTabView extends View
@surface.camera.zoomTo({x:262, y:-164}, 1.66, 0)
destroy: ->
super()
@selectAddThangType null
@surface.destroy()
super()
onViewSwitched: (e) ->
@selectAddThang()
@ -374,7 +374,7 @@ module.exports = class ThangsTabView extends View
thangData = @thangsTreema.get "id=#{e.thangID}"
@editThangView = new LevelThangEditView thangData: thangData, supermodel: @supermodel, level: @level, world: @world
@insertSubView @editThangView
@$el.find('.thangs-column').addClass('hide')
@$el.find('.thangs-column').hide()
Backbone.Mediator.publish 'level:view-switched', e
onLevelThangEdited: (e) ->
@ -383,7 +383,7 @@ module.exports = class ThangsTabView extends View
onLevelThangDoneEditing: ->
@removeSubView @editThangView
@$el.find('.thangs-column').removeClass('hide')
@$el.find('.thangs-column').show()
class ThangsNode extends TreemaNode.nodeMap.array

View file

@ -1,21 +1,21 @@
CocoView = require 'views/kinds/CocoView'
template = require 'templates/editor/thang/colors_tab'
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
{hexToHSL} = require 'lib/utils'
{hexToHSL} = require 'lib/utils'
module.exports = class ColorsTabView extends CocoView
id: 'editor-thang-colors-tab-view'
template: template
className: 'tab-pane'
offset: 0
constructor: (@thangType, options) ->
@thangType.once 'sync', @tryToBuild, @
@thangType.schema().once 'sync', @tryToBuild, @
@colorConfig = { hue: 0, saturation: 0.5, lightness: 0.5 }
@spriteBuilder = new SpriteBuilder(@thangType)
f = =>
f = =>
@offset++
@updateMovieClip()
@interval = setInterval f, 1000
@ -27,21 +27,21 @@ module.exports = class ColorsTabView extends CocoView
@initStage()
@initSliders()
@tryToBuild()
# sliders
initSliders: ->
@hueSlider = @initSlider $("#hue-slider", @$el), 0, @makeSliderCallback 'hue'
@saturationSlider = @initSlider $("#saturation-slider", @$el), 50, @makeSliderCallback 'saturation'
@lightnessSlider = @initSlider $("#lightness-slider", @$el), 50, @makeSliderCallback 'lightness'
makeSliderCallback: (property) ->
(e, result) =>
@colorConfig[property] = result.value / 100
@updateMovieClip()
# movie clip
initStage: ->
canvas = @$el.find('#tinting-display')
@stage = new createjs.Stage(canvas[0])
@ -95,7 +95,7 @@ module.exports = class ColorsTabView extends CocoView
aHSL = hexToHSL(a)
bHSL = hexToHSL(b)
if aHSL[0] > bHSL[0] then -1 else 1
for color in colors
button = $('<button></button>').addClass('btn')
button.css('background', color)
@ -130,23 +130,23 @@ module.exports = class ColorsTabView extends CocoView
@thangType.set('colorGroups', @colorGroups.data)
onColorGroupSelected: (e, selected) =>
@$el.find('#color-group-settings').toggleClass('hide', not selected.length)
@$el.find('#color-group-settings').toggle selected.length > 0
treema = @colorGroups.getLastSelectedTreema()
return unless treema
@currentColorGroupTreema = treema
shapes = {}
shapes[shape] = true for shape in treema.data
colors = {}
for key, shape of @thangType.get('raw')?.shapes or {}
continue unless shape.fc?
colors[shape.fc] = true if shapes[key]
@buttons.find('button').removeClass('selected')
@buttons.find('button').each (i, button) ->
$(button).addClass('selected') if colors[$(button).val()]
@updateMovieClip()
updateColorGroup: ->
@ -155,7 +155,7 @@ module.exports = class ColorsTabView extends CocoView
return unless $(button).hasClass('selected')
window.button = button
colors[$(button).val()] = true
shapes = []
for key, shape of @thangType.get('raw')?.shapes or {}
continue unless shape.fc?

View file

@ -29,6 +29,7 @@ module.exports = class ThangTypeEditView extends View
'change #real-upload-button': 'animationFileChosen'
'change #animations-select': 'showAnimation'
'click #marker-button': 'toggleDots'
'click #end-button': 'endAnimation'
subscriptions:
'save-new-version': 'saveNewThangType'
@ -52,7 +53,7 @@ module.exports = class ThangTypeEditView extends View
@files.fetch()
@render()
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.thangType = @thangType
context.animations = @getAnimationNames()
@ -97,6 +98,7 @@ module.exports = class ThangTypeEditView extends View
@stage = new createjs.Stage(canvas[0])
canvasWidth = parseInt(canvas.attr('width'), 10)
canvasHeight = parseInt(canvas.attr('height'), 10)
@camera?.destroy()
@camera = new Camera canvasWidth, canvasHeight
@torsoDot = @makeDot('blue')
@ -129,6 +131,9 @@ module.exports = class ThangTypeEditView extends View
@aboveHeadDot.y = CENTER.y + aboveHead.y * @scale
@stage.addChild(@groundDot, @torsoDot, @mouthDot, @aboveHeadDot)
endAnimation: ->
@currentSprite?.queueAction('idle')
updateGrid: ->
grid = new createjs.Container()
line = new createjs.Shape()
@ -371,3 +376,7 @@ module.exports = class ThangTypeEditView extends View
@grid.alpha = 1.0
@showAnimation()
@showingSelectedNode = false
destroy: ->
@camera?.destroy()
super()

View file

@ -2,6 +2,7 @@ View = require 'views/kinds/RootView'
template = require 'templates/home'
WizardSprite = require 'lib/surface/WizardSprite'
ThangType = require 'models/ThangType'
Simulator = require 'lib/simulator/Simulator'
module.exports = class HomeView extends View
id: 'home-view'
@ -10,6 +11,7 @@ module.exports = class HomeView extends View
events:
'mouseover #beginner-campaign': 'onMouseOverButton'
'mouseout #beginner-campaign': 'onMouseOutButton'
'click #simulate-button': 'onSimulateButtonClick'
getRenderData: ->
c = super()
@ -84,7 +86,7 @@ module.exports = class HomeView extends View
@wizardSprite?.queueAction 'idle'
updateStage: =>
@stage.update()
@stage?.update()
willDisappear: ->
super()
@ -94,3 +96,11 @@ module.exports = class HomeView extends View
didReappear: ->
super()
@turnOnStageUpdates()
destroy: ->
@wizardSprite?.destroy()
super()
onSimulateButtonClick: (e) =>
simulator = new Simulator()
simulator.fetchAndSimulateTask()

View file

@ -11,7 +11,7 @@ makeScopeName = -> "view-scope-#{classCount++}"
module.exports = class CocoView extends Backbone.View
startsLoading: false
cache: true # signals to the router to keep this view around
template: => ''
template: -> ''
events:
'click a': 'toggleModal'
@ -36,11 +36,15 @@ module.exports = class CocoView extends Backbone.View
super options
destroy: ->
@destroyed = true
@stopListening()
@stopListeningToShortcuts()
@undelegateEvents() # removes both events and subs
view.destroy() for id, view of @subviews
@modalClosed = null
$('#modal-wrapper .modal').off 'hidden.bs.modal', @modalClosed
@[key] = undefined for key, value of @
@destroyed = true
@destroy = ->
afterInsert: ->
@ -60,7 +64,7 @@ module.exports = class CocoView extends Backbone.View
# View Rendering
render: =>
render: ->
return @ unless me
super()
return @template if _.isString(@template)
@ -70,7 +74,7 @@ module.exports = class CocoView extends Backbone.View
@$el.i18n()
@
getRenderData: (context) =>
getRenderData: (context) ->
context ?= {}
context.isProduction = document.location.href.search(/codecombat.com/) isnt -1
context.me = me
@ -81,46 +85,40 @@ module.exports = class CocoView extends Backbone.View
context
afterRender: ->
@registerModalsWithin()
# Modals
toggleModal: (e) ->
toggleModal: (e) ->
if $(e.currentTarget).prop('target') is '_blank'
return true
# special handler for opening modals that are dynamically loaded, rather than static in the page. It works (or should work) like Bootstrap's modals, except use coco-modal for the data-toggle value.
# special handler for opening modals that are dynamically loaded, rather than static in the page. It works (or should work) like Bootstrap's modals, except use coco-modal for the data-toggle value.
elem = $(e.target)
return unless elem.data('toggle') is 'coco-modal'
target = elem.data('target')
view = application.router.getView(target, '_modal') # could set up a system for loading cached modals, if told to
view = application.router.getView(target, '_modal') # could set up a system for loading cached modals, if told to
@openModalView(view)
registerModalsWithin: (e...) ->
# TODO: Get rid of this part
for modal in $('.modal', @$el)
# console.warn 'Registered modal to get rid of...', modal
$(modal).on('show.bs.modal', @clearModals)
openModalView: (modalView) ->
return if @waitingModal # can only have one waiting at once
if visibleModal
waitingModal = modalView
visibleModal.hide()
return
modalView.render()
modalView.render()
$('#modal-wrapper').empty().append modalView.el
modalView.afterInsert()
visibleModal = modalView
modalOptions = {show: true, backdrop: if modalView.closesOnClickOutside then true else 'static'}
$('#modal-wrapper .modal').modal(modalOptions).on('hidden.bs.modal', => @modalClosed())
$('#modal-wrapper .modal').modal(modalOptions).on 'hidden.bs.modal', @modalClosed
window.currentModal = modalView
@getRootView().stopListeningToShortcuts(true)
modalClosed: =>
modalClosed: =>
visibleModal.willDisappear() if visibleModal
visibleModal.destroy()
visibleModal = null
if waitingModal
#$('#modal-wrapper .modal').off 'hidden.bs.modal', @modalClosed
if waitingModal
wm = waitingModal
waitingModal = null
@openModalView(wm)
@ -128,16 +126,10 @@ module.exports = class CocoView extends Backbone.View
@getRootView().listenToShortcuts(true)
Backbone.Mediator.publish 'modal-closed'
clearModals: =>
if visibleModal
visibleModal.$el.addClass('hide')
waitingModal = null
@modalClosed()
# Loading RootViews
showLoading: ($el=@$el) ->
$el.find('>').addClass('hide')
$el.find('>').hide()
$el.append($('<div class="loading-screen"></div>')
.append('<h2>Loading</h2>')
.append('<div class="progress progress-striped active loading"><div class="progress-bar"></div></div>'))
@ -146,18 +138,20 @@ module.exports = class CocoView extends Backbone.View
hideLoading: ->
return unless @_lastLoading?
@_lastLoading.find('.loading-screen').remove()
@_lastLoading.find('>').removeClass('hide')
@_lastLoading.find('>').show()
@_lastLoading = null
# Loading ModalViews
enableModalInProgress: (modal) ->
$('> div', modal).addClass('hide')
$('.wait', modal).removeClass('hide')
el = modal.find('.modal-content')
el.find('> div', modal).hide()
el.find('.wait', modal).show()
disableModalInProgress: (modal) ->
$('> div', modal).removeClass('hide')
$('.wait', modal).addClass('hide')
el = modal.find('.modal-content')
el.find('> div', modal).show()
el.find('.wait', modal).hide()
# Subscriptions
@ -224,8 +218,8 @@ module.exports = class CocoView extends Backbone.View
slider.on('slide',changeCallback)
slider.on('slidechange',changeCallback)
slider
mobileRELong = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i
mobileREShort = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i

View file

@ -16,7 +16,7 @@ module.exports = class ModalView extends CocoView
@modalWidthPercent = options.modalWidthPercent if options.modalWidthPercent
super options
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.closeButton = @closeButton
context

View file

@ -34,7 +34,7 @@ module.exports = class SuperVersionsView extends View
@startsLoading = false
@render()
getRenderData: (context={}) =>
getRenderData: (context={}) ->
context = super(context)
context.page = @page
context.dataList = (m.attributes for m in @collection.models) if @collection

View file

@ -36,7 +36,6 @@ module.exports = class LoginModalView extends View
loginAccount: (e) =>
forms.clearFormAlerts(@$el)
userObject = forms.formToObject @$el
# res = validateUser(userObject)
res = tv4.validateMultiple userObject, User.schema.attributes
return forms.applyErrorsToForm(@$el, res.errors) unless res.valid
@enableModalInProgress(@$el) # TODO: part of forms

View file

@ -76,5 +76,5 @@ module.exports = class WizardSettingsView extends View
me.save()
destroy: ->
super()
@wizardSprite?.destroy()
super()

View file

@ -21,11 +21,28 @@ class LeaderboardCollection extends CocoCollection
super()
options ?= {}
@url = "/db/level/#{level.get('original')}.#{level.get('version').major}/leaderboard?#{$.param(options)}"
#@url = "/db/level/#{level.get('original')}/leaderboard?#{$.param(options)}"
module.exports = class LadderView extends RootView
id: 'ladder-view'
template: require 'templates/play/ladder'
startsLoading: true
events:
'click #simulate-button': 'onSimulateButtonClick'
onSimulateButtonClick: (e) ->
submitIDs = _.pluck @leaderboards[@teams[0]].topPlayers.models, "id"
for ID in submitIDs
$.ajax
url: '/queue/scoring'
method: 'POST'
data:
session: ID
alert "Simulating all games!"
alert "(do not push more than once pls)"
constructor: (options, levelID) ->
super(options)
@ -33,15 +50,16 @@ module.exports = class LadderView extends RootView
@level.fetch()
@level.once 'sync', @onLevelLoaded, @
@sessions = new LevelSessionsCollection(levelID)
@sessions.fetch({})
@sessions.once 'sync', @onMySessionsLoaded, @
# @sessions = new LevelSessionsCollection(levelID)
# @sessions.fetch({})
# @sessions.once 'sync', @onMySessionsLoaded, @
onLevelLoaded: -> @startLoadingPhaseTwoMaybe()
onMySessionsLoaded: -> @startLoadingPhaseTwoMaybe()
onMySessionsLoaded: ->
@startLoadingPhaseTwoMaybe()
startLoadingPhaseTwoMaybe: ->
return unless @level.loaded and @sessions.loaded
return unless @level.loaded # and @sessions.loaded
@loadPhaseTwo()
loadPhaseTwo: ->
@ -55,17 +73,19 @@ module.exports = class LadderView extends RootView
@leaderboards = {}
@challengers = {}
for team in teams
teamSession = _.find @sessions.models, (session) -> session.get('team') is team
# teamSession = _.find @sessions.models, (session) -> session.get('team') is team
teamSession = null
console.log "Team session: #{JSON.stringify teamSession}"
@leaderboards[team] = new LeaderboardData(@level, team, teamSession)
@leaderboards[team].once 'sync', @onLeaderboardLoaded, @
@challengers[team] = new ChallengersData(@level, team, teamSession)
@challengers[team].once 'sync', @onChallengersLoaded, @
# @challengers[team] = new ChallengersData(@level, team, teamSession)
# @challengers[team].once 'sync', @onChallengersLoaded, @
onChallengersLoaded: -> @renderMaybe()
onLeaderboardLoaded: -> @renderMaybe()
renderMaybe: ->
loaders = _.values(@leaderboards).concat(_.values(@challengers))
loaders = _.values(@leaderboards) # .concat(_.values(@challengers))
return unless _.every loaders, (loader) -> loader.loaded
@startsLoading = false
@render()
@ -78,13 +98,15 @@ module.exports = class LadderView extends RootView
ctx.link = "/play/level/#{@level.get('name')}"
ctx.teams = []
for team in @teams or []
otherTeam = if team is 'ogres' then 'humans' else 'ogres'
ctx.teams.push({
id: team
name: _.string.titleize(team)
leaderboard: @leaderboards[team]
easyChallenger: @challengers[team].easyPlayer.models[0]
mediumChallenger: @challengers[team].mediumPlayer.models[0]
hardChallenger: @challengers[team].hardPlayer.models[0]
otherTeam: otherTeam
# easyChallenger: @challengers[team].easyPlayer.models[0]
# mediumChallenger: @challengers[team].mediumPlayer.models[0]
# hardChallenger: @challengers[team].hardPlayer.models[0]
})
ctx
@ -94,23 +116,28 @@ module.exports = class LadderView extends RootView
class LeaderboardData
constructor: (@level, @team, @session) ->
console.log 'creating leaderboard data', @level, @team, @session
_.extend @, Backbone.Events
@topPlayers = new LeaderboardCollection(@level, {order:-1, scoreOffset: HIGHEST_SCORE, team: @team, limit: if @session then 10 else 20})
@topPlayers.fetch()
@topPlayers.comparator = (model) ->
return -model.get('totalScore')
@topPlayers.sort()
@topPlayers.once 'sync', @leaderboardPartLoaded, @
if @session
score = @session.get('score') or 25
@playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
@playersAbove.fetch()
@playersAbove.once 'sync', @leaderboardPartLoaded, @
@playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
@playersBelow.fetch()
@playersBelow.once 'sync', @leaderboardPartLoaded, @
# if @session
# score = @session.get('totalScore') or 25
# @playersAbove = new LeaderboardCollection(@level, {order:1, scoreOffset: score, limit: 4, team: @team})
# @playersAbove.fetch()
# @playersAbove.once 'sync', @leaderboardPartLoaded, @
# @playersBelow = new LeaderboardCollection(@level, {order:-1, scoreOffset: score, limit: 4, team: @team})
# @playersBelow.fetch()
# @playersBelow.once 'sync', @leaderboardPartLoaded, @
leaderboardPartLoaded: ->
if @session
if @topPlayers.loaded and @playersAbove.loaded and @playersBelow.loaded
if @topPlayers.loaded # and @playersAbove.loaded and @playersBelow.loaded
@loaded = true
@trigger 'sync'
else
@ -120,7 +147,7 @@ class LeaderboardData
class ChallengersData
constructor: (@level, @team, @session) ->
_.extend @, Backbone.Events
score = @session?.get('score') or 25
score = @session?.get('totalScore') or 25
@easyPlayer = new LeaderboardCollection(@level, {order:1, scoreOffset: score - 5, limit: 1, team: @team})
@easyPlayer.fetch()
@easyPlayer.once 'sync', @challengerLoaded, @

View file

@ -37,6 +37,7 @@ module.exports = class ControlBarView extends View
setBus: (@bus) ->
onPlayerStatesChanged: (e) ->
# TODO: this doesn't fire any more. Replacement?
return unless @bus is e.bus
numPlayers = _.keys(e.players).length
return if numPlayers is @numPlayers
@ -45,7 +46,7 @@ module.exports = class ControlBarView extends View
text += " (#{numPlayers})" if numPlayers > 1
$('#multiplayer-button', @$el).text(text)
getRenderData: (context={}) =>
getRenderData: (context={}) ->
super context
context.worldName = @worldName
context.multiplayerEnabled = @session.get('multiplayer')
@ -56,7 +57,7 @@ module.exports = class ControlBarView extends View
@openModalView(new DocsModal(options))
showMultiplayerModal: ->
@openModalView(new MultiplayerModal(session: @session, playableTeams: @playableTeams))
@openModalView(new MultiplayerModal(session: @session, playableTeams: @playableTeams, level: @level))
showRestartModal: ->
@openModalView(new ReloadModal())

View file

@ -18,7 +18,7 @@ module.exports = class GoalsView extends View
events:
'click': 'toggleCollapse'
toggleCollapse: (e) =>
toggleCollapse: (e) ->
@$el.toggleClass('expanded').toggleClass('collapsed')
onNewGoalStates: (e) ->
@ -28,6 +28,7 @@ module.exports = class GoalsView extends View
for goal in e.goals
state = e.goalStates[goal.id]
continue if goal.hiddenGoal and state.status isnt 'failure'
continue if goal.team and me.team isnt goal.team
text = goal.i18n?[me.lang()]?.name ? goal.name
if state.killed
dead = _.filter(_.values(state.killed)).length
@ -45,11 +46,11 @@ module.exports = class GoalsView extends View
li.prepend($('<i></i>').addClass(stateIconMap[state.status]))
list.append(li)
goals.push goal
if goals.length then @$el.removeClass('hide') else @$el.addClass('hide')
@$el.removeClass('secret') if goals.length > 0
render: ->
super()
@$el.addClass('hide').addClass('expanded')
@$el.addClass('secret').addClass('expanded')
onSetLetterbox: (e) ->
if e.on then @$el.hide() else @$el.show()
@$el.toggle not e.on

View file

@ -0,0 +1,22 @@
View = require 'views/kinds/CocoView'
template = require 'templates/play/level/gold'
module.exports = class GoldView extends View
id: "gold-view"
template: template
subscriptions:
'surface:gold-changed': 'onGoldChanged'
'level-set-letterbox': 'onSetLetterbox'
onGoldChanged: (e) ->
@$el.show()
goldEl = @$el.find('.gold-amount.team-' + e.team)
unless goldEl.length
teamEl = $("<h3 class='team-#{e.team}' title='Gold: #{e.team}'><img src='/images/level/prop_gold.png'> <div class='gold-amount team-#{e.team}'></div>")
@$el.append(teamEl)
goldEl = teamEl.find('.gold-amount.team-' + e.team)
goldEl.text(e.gold)
onSetLetterbox: (e) ->
@$el.toggle not e.on

View file

@ -24,12 +24,15 @@ module.exports = class HUDView extends View
'god:new-world-created': 'onNewWorld'
events:
'click': -> Backbone.Mediator.publish 'focus-editor'
'click': 'onClick'
afterRender: =>
afterRender: ->
super()
@$el.addClass 'no-selection'
onClick: (e) ->
Backbone.Mediator.publish 'focus-editor' unless $(e.target).parents('.thang-props').length
onFrameChanged: (e) ->
@timeProgress = e.progress
@update()
@ -153,7 +156,7 @@ module.exports = class HUDView extends View
@bubble.removeClass(@lastMood) if @lastMood
@lastMood = mood
@bubble.text('')
group = $('<div class="enter hide"></div>')
group = $('<div class="enter secret"></div>')
@bubble.append(group)
if responses
@lastResponses = responses
@ -176,7 +179,7 @@ module.exports = class HUDView extends View
if @animator.done()
clearInterval(@messageInterval)
@messageInterval = null
$('.enter', @bubble).removeClass("hide").css('opacity', 0.0).delay(500).animate({opacity:1.0}, 500, @animateEnterButton)
$('.enter', @bubble).removeClass("secret").css('opacity', 0.0).delay(500).animate({opacity:1.0}, 500, @animateEnterButton)
if @lastResponses
buttons = $('.enter button')
for response, i in @lastResponses
@ -207,10 +210,10 @@ module.exports = class HUDView extends View
switchToDialogueElements: ->
@dialogueMode = true
$('.thang-elem', @$el).addClass('hide')
@$el.find('.thang-canvas-wrapper').removeClass('hide')
$('.thang-elem', @$el).addClass('secret')
@$el.find('.thang-canvas-wrapper').removeClass('secret')
$('.dialogue-area', @$el)
.removeClass('hide')
.removeClass('secret')
.animate({opacity:1.0}, 200)
$('.dialogue-bubble', @$el)
.css('opacity', 0.0)
@ -220,8 +223,8 @@ module.exports = class HUDView extends View
switchToThangElements: ->
@dialogueMode = false
$('.thang-elem', @$el).removeClass('hide')
$('.dialogue-area', @$el).addClass('hide')
$('.thang-elem', @$el).removeClass('secret')
$('.dialogue-area', @$el).addClass('secret')
update: ->
return unless @thang and not @speaker
@ -235,7 +238,7 @@ module.exports = class HUDView extends View
return null # included in the bar
context =
prop: prop
hasIcon: prop in ["health", "pos", "target", "inventory"]
hasIcon: prop in ["health", "pos", "target", "inventory", "gold"]
hasBar: prop in ["health"]
$(prop_template(context))
@ -321,7 +324,7 @@ module.exports = class HUDView extends View
changed = true
break
return unless changed
ael.toggleClass 'hidden', not timespans.length
ael.toggleClass 'secret', not timespans.length
@lastActionTimespans[action] = timespans
timeline = ael.find('.action-timeline .timeline-wrapper').empty()
lifespan = @thang.world.totalFrames / @thang.world.frameRate
@ -333,5 +336,8 @@ module.exports = class HUDView extends View
ael
destroy: ->
super()
@stage?.stopTalking()
@addMoreMessage = null
@animateEnterButton = null
clearInterval(@messageInterval) if @messageInterval
super()

View file

@ -18,19 +18,16 @@ module.exports = class LevelChatView extends View
constructor: (options) ->
@levelID = options.levelID
@session = options.session
@session.on 'change:multiplayer', @updateMultiplayerVisibility
@session.on 'change:multiplayer', @updateMultiplayerVisibility, @
@sessionID = options.sessionID
@bus = LevelBus.get(@levelID, @sessionID)
super()
@regularlyClearOldMessages()
@playNoise = _.debounce(@playNoise, 100)
updateMultiplayerVisibility: =>
updateMultiplayerVisibility: ->
return unless @$el?
if @session.get('multiplayer')
@$el.removeClass('hide')
else
@$el.addClass('hide')
@$el.toggle Boolean @session.get('multiplayer')
afterRender: ->
@chatTables = $('table', @$el)
@ -47,8 +44,8 @@ module.exports = class LevelChatView extends View
if new Date().getTime() - added > 60 * 1000
row.fadeOut(1000, -> $(this).remove())
onNewMessage: (e) =>
@$el.removeClass('hide') unless e.message.system
onNewMessage: (e) ->
@$el.show() unless e.message.system
@addOne(e.message)
@trimClosedPanel()
@playNoise() if e.message.authorID isnt me.id
@ -99,7 +96,7 @@ module.exports = class LevelChatView extends View
break if rows.length - i <= limit
row.remove()
onChatKeydown: (e) =>
onChatKeydown: (e) ->
if key.isPressed('enter')
message = _.string.strip($(e.target).val())
return false unless message
@ -107,17 +104,11 @@ module.exports = class LevelChatView extends View
$(e.target).val('')
return false
onIconClick: =>
openPanel = $('.open-chat-area', @$el)
closedPanel = $('.closed-chat-area', @$el)
onIconClick: ->
@open = not @open
if @open
closedPanel.addClass('hide')
openPanel.removeClass('hide')
@scrollDown()
else
openPanel.addClass('hide')
closedPanel.removeClass('hide')
openPanel = $('.open-chat-area', @$el).toggle @open
closedPanel = $('.closed-chat-area', @$el).toggle not @open
@scrollDown() if @open
if window.getSelection?
sel = window.getSelection()
sel.empty?()
@ -130,6 +121,8 @@ module.exports = class LevelChatView extends View
openPanel.scrollTop = openPanel.scrollHeight or 1000000
destroy: ->
console.log('DESTROY CHAT', @levelID)
super()
key.deleteScope('level')
@session.off 'change:multiplayer', @updateMultiplayerVisibility, @
clearInterval @clearOldMessagesInterval if @clearOldMessagesInterval
@clearOldMessages = null
super()

View file

@ -9,11 +9,13 @@ module.exports = class MultiplayerModal extends View
events:
'click textarea': 'onClickLink'
'change #multiplayer': 'updateLinkSection'
'click #submit-session-button': 'submitSession'
constructor: (options) ->
super(options)
@session = options.session
@session.on 'change:multiplayer', @updateLinkSection
@level = options.level
@session.on 'change:multiplayer', @updateLinkSection, @
@playableTeams = options.playableTeams
getRenderData: ->
@ -23,6 +25,7 @@ module.exports = class MultiplayerModal extends View
@session.id)
c.multiplayer = @session.get('multiplayer')
c.playableTeams = @playableTeams
c.ladderGame = @level?.get('name') is 'Project DotA' and not me.get('isAnonymous')
c
afterRender: ->
@ -32,12 +35,23 @@ module.exports = class MultiplayerModal extends View
onClickLink: (e) ->
e.target.select()
updateLinkSection: =>
updateLinkSection: ->
multiplayer = @$el.find('#multiplayer').prop('checked')
la = @$el.find('#link-area')
if multiplayer then la.show() else la.hide()
la.toggle Boolean(multiplayer)
true
submitSession: ->
$.ajax('/queue/scoring', {
method: 'POST'
data:
session: @session.id
})
onHidden: ->
multiplayer = Boolean(@$el.find('#multiplayer').prop('checked'))
@session.set('multiplayer', multiplayer)
destroy: ->
@session.off 'change:multiplayer', @updateLinkSection, @
super()

View file

@ -100,8 +100,8 @@ module.exports = class VictoryModal extends View
Backbone.Mediator.publish 'level:victory-hidden'
destroy: ->
super()
@saveReview() if @$el.find('.review textarea').val()
super()
# rating, review

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