Merge pull request #1 from codecombat/master

Fixes error "Type str doesn't support the buffer API"
This commit is contained in:
Akhil-T 2014-02-26 23:01:07 -06:00
commit 312d880508
94 changed files with 519 additions and 169 deletions

View file

@ -132,7 +132,7 @@ module.exports = class God
angel.destroy() for angel in @angels
@dead = true
Backbone.Mediator.unsubscribe('tome:cast-spells', @onTomeCast, @)
@goalManager.destroy()
@goalManager?.destroy()
@goalManager = null
@fillWorkerPool = null
@simulateWorld = null

View file

@ -87,6 +87,9 @@ module.exports = class LevelBus extends Bus
# LevelSession object. Either break this off into a separate class
# or have the LevelSession object listen for all these events itself.
setSpells: (spells) ->
@onSpellCreated spell: spell for spellKey, spell of spells
onSpellChanged: (e) ->
return unless @onPoint()
code = @session.get('code')
@ -102,6 +105,7 @@ module.exports = class LevelBus extends Bus
onSpellCreated: (e) ->
return unless @onPoint()
spellTeam = e.spell.team
@teamSpellMap ?= {}
@teamSpellMap[spellTeam] ?= []
unless e.spell.spellKey in @teamSpellMap[spellTeam]
@ -109,8 +113,8 @@ module.exports = class LevelBus extends Bus
@changedSessionProperties.teamSpells = true
@session.set({'teamSpells': @teamSpellMap})
@saveSession()
if spellTeam is me.team
@onSpellChanged e # Save the new spell to the session, too.
onScriptStateChanged: (e) ->
return unless @onPoint()
@ -236,10 +240,3 @@ module.exports = class LevelBus extends Bus
destroy: ->
@session.off 'change:multiplayer', @onMultiplayerChanged, @
super()
setTeamSpellMap: (spellMap) ->
@teamSpellMap = spellMap
console.log @teamSpellMap
@changedSessionProperties.teamSpells = true
@session.set({'teamSpells': @teamSpellMap})
@saveSession()

View file

@ -93,9 +93,6 @@ module.exports = class LevelLoader extends CocoClass
@supermodel.populateModel @level
onSupermodelError: ->
msg = $.i18n.t('play_level.level_load_error',
defaultValue: "Level could not be loaded.")
$('body').append('<div class="alert">' + msg + '</div>')
onSupermodelLoadedOne: (e) ->
@update()
@ -113,16 +110,19 @@ module.exports = class LevelLoader extends CocoClass
@updateCompleted = true
denormalizeSession: ->
return if @session.get 'levelName'
return if @sessionDenormalized
patch =
'levelName': @level.get('name')
'levelID': @level.get('slug') or @level.id
if me.id is @session.get 'creator'
patch.creatorName = me.get('name')
@session.set key, value for key, value of patch
tempSession = new LevelSession _id: @session.id
tempSession.save(patch, {patch: true})
for key, value of patch
if @session.get(key) is value
delete patch[key]
unless _.isEmpty patch
@session.set key, value for key, value of patch
tempSession = new LevelSession _id: @session.id
tempSession.save(patch, {patch: true})
@sessionDenormalized = true
# World init

View file

@ -132,6 +132,15 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass
continue unless script.channel is channel
continue if alreadyTriggered and not script.repeats
continue if script.lastTriggered? and new Date().getTime() - script.lastTriggered < 1
continue if script.neverRun
if script.notAfter
for scriptID in script.notAfter
if scriptID in @triggered
script.neverRun = true
break
continue if script.neverRun
continue unless @scriptPrereqsSatisfied(script)
continue unless scriptMatchesEventPrereqs(script, event)
# everything passed!

View file

@ -33,7 +33,7 @@ module.exports = class SpritesScriptModule extends ScriptModule
spriteSayNote: (sprite, script) ->
return if @speakingSprites[sprite.id]
responses = sprite.say.responses
responses = [] unless script.skippable
responses = [] unless script.skippable or responses
for response in responses ? []
response.text = response.i18n?[me.lang()]?.text ? response.text
text = sprite.say.i18n?[me.lang()]?.text or sprite.say.text

View file

@ -64,7 +64,7 @@ module.exports = class Simulator
setupGoalManager: ->
@god.goalManager = new GoalManager @world
@god.goalManager.goals = @fetchGoalsFromWorldNoteChain()
@god.goalManager.goals = @god.level.goals
@god.goalManager.goalStates = @manuallyGenerateGoalStates()
commenceSimulationAndSetupCallback: ->
@ -108,17 +108,22 @@ module.exports = class Simulator
taskResults =
taskID: @task.getTaskID()
receiptHandle: @task.getReceiptHandle()
originalSessionID: @task.getFirstSessionID()
originalSessionRank: -1
calculationTime: 500
sessions: []
for session in @task.getSessions()
sessionResult =
sessionID: session.sessionID
submitDate: session.submitDate
creator: session.creator
metrics:
rank: @calculateSessionRank session.sessionID, simulationResults.goalStates, @task.generateTeamToSessionMap()
if session.sessionID is taskResults.originalSessionID
taskResults.originalSessionRank = sessionResult.metrics.rank
taskResults.originalSessionTeam = session.team
taskResults.sessions.push sessionResult
return taskResults
@ -137,8 +142,6 @@ module.exports = class Simulator
else
return 1
fetchGoalsFromWorldNoteChain: -> return @god.goalManager.world.scripts[0].noteChain[0].goals.add
manuallyGenerateGoalStates: ->
goalStates =
"destroy-humans":
@ -178,7 +181,6 @@ module.exports = class Simulator
generateSpellKeyToSourceMapPropertiesFromThang: (thang) =>
for component in thang.components
continue unless @componentHasProgrammableMethods component
for methodName, method of component.config.programmableMethods
spellKey = @generateSpellKeyFromThangIDAndMethodName thang.id, methodName
@ -187,9 +189,11 @@ module.exports = class Simulator
@transpileSpell thang, spellKey, methodName
generateSpellKeyFromThangIDAndMethodName: (thang, methodName) ->
spellKeyComponents = [thang.id, methodName]
spellKeyComponents = [thang, methodName]
spellKeyComponents[0] = _.string.slugify spellKeyComponents[0]
spellKeyComponents.join '/'
spellKey = spellKeyComponents.join '/'
spellKey
createSpellAndAssignName: (spellKey, spellName) ->
@spells[spellKey] ?= {}
@ -202,7 +206,7 @@ module.exports = class Simulator
transpileSpell: (thang, spellKey, methodName) ->
slugifiedThangID = _.string.slugify thang.id
source = @currentUserCodeMap[slugifiedThangID]?[methodName] ? ""
source = @currentUserCodeMap[[slugifiedThangID,methodName].join '/'] ? ""
@spells[spellKey].thangs[thang.id].aether.transpile source
createAether: (methodName, method) ->
@ -220,7 +224,7 @@ module.exports = class Simulator
#functionParameters: # TODOOOOO
if methodName is 'hear'
aetherOptions.functionParameters = ['speaker', 'message', 'data']
console.log "creating aether with options", aetherOptions
#console.log "creating aether with options", aetherOptions
return new Aether aetherOptions
class SimulationTask
@ -253,12 +257,18 @@ class SimulationTask
generateSpellKeyToSourceMap: ->
spellKeyToSourceMap = {}
for session in @rawData.sessions
teamSpells = session.teamSpells[session.team]
_.merge spellKeyToSourceMap, _.pick(session.code, teamSpells)
teamCode = {}
for thangName, thangSpells of session.code
for spellName, spell of thangSpells
fullSpellName = [thangName,spellName].join '/'
if _.contains(teamSpells, fullSpellName)
teamCode[fullSpellName]=spell
_.merge spellKeyToSourceMap, teamCode
commonSpells = session.teamSpells["common"]
_.merge spellKeyToSourceMap, _.pick(session.code, commonSpells) if commonSpells?
spellKeyToSourceMap

View file

@ -40,6 +40,8 @@ module.exports = class Camera extends CocoClass
'camera-zoom-out': 'onZoomOut'
'surface:mouse-scrolled': 'onMouseScrolled'
'level:restarted': 'onLevelRestarted'
'sprite:mouse-down': 'onMouseDown'
'sprite:dragged': 'onMouseDragged'
# TODO: Fix tests to not use mainLayer
constructor: (@canvasWidth, @canvasHeight, angle=Math.asin(0.75), hFOV=d2r(30)) ->
@ -164,6 +166,21 @@ module.exports = class Camera extends CocoClass
target = @target
@zoomTo target, newZoom, 0
onMouseDown: (e) ->
return if @dragDisabled
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
onMouseDragged: (e) ->
return if @dragDisabled
target = @boundTarget(@target, @zoom)
newPos = {
x: target.x + (@lastPos.x - e.originalEvent.rawX) / @zoom
y: target.y + (@lastPos.y - e.originalEvent.rawY) / @zoom
}
@zoomTo newPos, @zoom, 0
@lastPos = {x: e.originalEvent.rawX, y: e.originalEvent.rawY}
Backbone.Mediator.publish 'camera:dragged'
onLevelRestarted: ->
@setBounds(@firstBounds, false)

View file

@ -55,7 +55,7 @@ module.exports = class CoordinateDisplay extends createjs.Container
@label.regY = height / 2
sup = @camera.worldToSurface @lastPos
@x = sup.x
@y = sup.y
@y = sup.y - 7
@addChild @label
@cache -width / 2, -height / 2, width, height
Backbone.Mediator.publish 'surface:coordinates-shown', {}

View file

@ -22,6 +22,7 @@ module.exports = IndieSprite = class IndieSprite extends CocoSprite
thang.width = thang.height = thang.depth = 4
thang.pos = pos ? @defaultPos()
thang.pos.z = thang.depth / 2
thang.shape = 'ellipsoid'
thang.rotation = 0
thang.action = 'idle'
thang.setAction = (action) -> thang.action = action

View file

@ -0,0 +1,40 @@
CocoClass = require 'lib/CocoClass'
module.exports = class PlaybackoverScreen extends CocoClass
constructor: (options) ->
super()
options ?= {}
@camera = options.camera
@layer = options.layer
console.error @toString(), "needs a camera." unless @camera
console.error @toString(), "needs a layer." unless @layer
@build()
toString: -> "<PlaybackoverScreen>"
build: ->
@dimLayer = new createjs.Container()
@dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
@dimLayer.layerIndex = -12
@dimLayer.addChild @dimScreen = new createjs.Shape()
@dimScreen.graphics.beginFill("rgba(0,0,0,0.4)").rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
@dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight
@dimLayer.alpha = 0
@layer.addChild @dimLayer
show: ->
console.log 'show playback over screen', @showing
return if @showing
@showing = true
@dimLayer.alpha = 0
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha:1}, 500)
hide: ->
console.log 'hide playback over screen', @showing
return unless @showing
@showing = false
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha:0}, 500)

View file

@ -5,6 +5,7 @@ module.exports = class PointChooser extends CocoClass
super()
@buildShape()
@options.stage.addEventListener 'stagemousedown', @onMouseDown
@options.camera.dragDisabled = true
destroy: ->
@options.stage.removeEventListener 'stagemousedown', @onMouseDown

View file

@ -7,6 +7,7 @@ module.exports = class RegionChooser extends CocoClass
@options.stage.addEventListener 'stagemousedown', @onMouseDown
@options.stage.addEventListener 'stagemousemove', @onMouseMove
@options.stage.addEventListener 'stagemouseup', @onMouseUp
@options.camera.dragDisabled = true
destroy: ->
@options.stage.removeEventListener 'stagemousedown', @onMouseDown

View file

@ -13,7 +13,7 @@ module.exports = class SpriteBoss extends CocoClass
'bus:player-left': 'onPlayerLeft'
'level-set-debug': 'onSetDebug'
'level-highlight-sprites': 'onHighlightSprites'
'sprite:mouse-down': 'onSpriteMouseDown'
'sprite:mouse-up': 'onSpriteMouseUp'
'surface:stage-mouse-down': 'onStageMouseDown'
'level-select-sprite': 'onSelectSprite'
'level-suppress-selection-sounds': 'onSuppressSelectionSounds'
@ -21,9 +21,11 @@ module.exports = class SpriteBoss extends CocoClass
'level:restarted': 'onLevelRestarted'
'god:new-world-created': 'onNewWorld'
'tome:cast-spells': 'onCastSpells'
'camera:dragged': 'onCameraDragged'
constructor: (@options) ->
super()
@dragged = 0
@options ?= {}
@camera = @options.camera
@surfaceLayer = @options.surfaceLayer
@ -183,6 +185,11 @@ module.exports = class SpriteBoss extends CocoClass
sprite.hasMoved = false
@removeSprite sprite if missing
@cache true if updateCache and @cached
# mainly for handling selecting thangs from session when the thang is not always in existence
if @willSelectThang and @sprites[@willSelectThang[0]]
@selectThang @willSelectThang...
@willSelectThang = null
cache: (update=false) ->
return if @cached and not update
@ -206,11 +213,16 @@ module.exports = class SpriteBoss extends CocoClass
onNewWorld: (e) ->
@world = @options.world = e.world
@play()
onCastSpells: -> @stop()
play: ->
sprite.imageObject.play() for thangID, sprite of @sprites
@selectionMark?.play()
@targetMark?.play()
onCastSpells: ->
stop: ->
sprite.imageObject.stop() for thangID, sprite of @sprites
@selectionMark?.stop()
@targetMark?.stop()
@ -226,8 +238,13 @@ module.exports = class SpriteBoss extends CocoClass
onSelectSprite: (e) ->
@selectThang e.thangID, e.spellName
onSpriteMouseDown: (e) ->
onCameraDragged: ->
@dragged += 1
onSpriteMouseUp: (e) ->
return if key.shift and @options.choosing
return @dragged = 0 if @dragged > 3
@dragged = 0
sprite = if e.sprite?.thang?.isSelectable then e.sprite else null
@selectSprite e, sprite
@ -236,6 +253,7 @@ module.exports = class SpriteBoss extends CocoClass
@selectSprite e if e.onBackground
selectThang: (thangID, spellName=null) ->
return @willSelectThang = [thangID, spellName] unless @sprites[thangID]
@selectSprite null, @sprites[thangID], spellName
selectSprite: (e, sprite=null, spellName=null) ->

View file

@ -9,6 +9,7 @@ Layer = require './Layer'
Letterbox = require './Letterbox'
Dimmer = require './Dimmer'
CastingScreen = require './CastingScreen'
PlaybackOverScreen = require './PlaybackOverScreen'
DebugDisplay = require './DebugDisplay'
CoordinateDisplay = require './CoordinateDisplay'
SpriteBoss = require './SpriteBoss'
@ -90,6 +91,7 @@ module.exports = Surface = class Surface extends CocoClass
@chooser?.destroy()
@dimmer?.destroy()
@castingScreen?.destroy()
@playbackOverScreen?.destroy()
@stage.clear()
@musicPlayer?.destroy()
@stage.removeAllChildren()
@ -179,7 +181,7 @@ module.exports = Surface = class Surface extends CocoClass
container.addChild shape
setProgress: (progress, scrubDuration=500) ->
progress = Math.max(Math.min(progress, 0.99), 0.0)
progress = Math.max(Math.min(progress, 1), 0.0)
@scrubbing = true
onTweenEnd = =>
@ -193,7 +195,7 @@ module.exports = Surface = class Surface extends CocoClass
createjs.Tween.removeTweens(@)
@currentFrame = @scrubbingTo
@scrubbingTo = Math.floor(progress * @world.totalFrames)
@scrubbingTo = Math.min(Math.floor(progress * @world.totalFrames), @world.totalFrames)
@scrubbingPlaybackSpeed = Math.sqrt(Math.abs(@scrubbingTo - @currentFrame) * @world.dt / (scrubDuration or 0.5))
if scrubDuration
t = createjs.Tween
@ -298,6 +300,18 @@ module.exports = Surface = class Surface extends CocoClass
frame: @currentFrame
world: @world
)
if @lastFrame < @world.totalFrames and @currentFrame >= @world.totalFrames
@spriteBoss.stop()
@playbackOverScreen.show()
@ended = true
Backbone.Mediator.publish 'surface:playback-ended'
else if @currentFrame < @world.totalFrames and @ended
@spriteBoss.play()
@playbackOverScreen.hide()
@ended = false
Backbone.Mediator.publish 'surface:playback-restarted'
@lastFrame = @currentFrame
onCastSpells: (event) ->
@ -311,7 +325,10 @@ module.exports = Surface = class Surface extends CocoClass
onNewWorld: (event) ->
return unless event.world.name is @world.name
@casting = false
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan }
# This has a tendency to break scripts that are waiting for playback to change when the level is loaded
# so only run it after the first world is created.
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan } unless event.firstWorld
fastForwardTo = null
if @playing
@ -350,6 +367,7 @@ module.exports = Surface = class Surface extends CocoClass
@screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight
@spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible
@castingScreen ?= new CastingScreen camera: @camera, layer: @screenLayer
@playbackOverScreen ?= new PlaybackOverScreen camera: @camera, layer: @screenLayer
@stage.enableMouseOver(10)
@stage.addEventListener 'stagemousemove', @onMouseMove
@stage.addEventListener 'stagemousedown', @onMouseDown

View file

@ -37,7 +37,7 @@ module.exports = class WizardSprite extends IndieSprite
makeIndieThang: (thangType, thangID, pos) ->
thang = super thangType, thangID, pos
thang.isSelectable = false
thang.bobHeight = 1.5
thang.bobHeight = 0.75
thang.bobTime = 2
thang.pos.z += thang.bobHeight
thang

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "العربية", englishDescription: "Arabi
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "български език", englishDescri
# gravatar_profile_link: "Full Gravatar Profile"
play_level:
level_load_error: "Нивото не може да бъде заредено."
level_load_error: "Нивото не може да бъде заредено: "
done: "Готово"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "čeština", englishDescription: "Czech", tr
gravatar_profile_link: "Účet Gravatar"
play_level:
level_load_error: "Úroveň se nepodařilo otevřít."
level_load_error: "Úroveň se nepodařilo otevřít: "
done: "Hotovo"
grid: "Mřížka"
customize_wizard: "Upravit Kouzelníka"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "dansk", englishDescription: "Danish", trans
gravatar_profile_link: "Fuld Gravatar Profil"
play_level:
level_load_error: "Banen kunne ikke indlæses."
level_load_error: "Banen kunne ikke indlæses: "
done: "Færdig"
grid: "Gitter"
customize_wizard: "Tilpas troldmand"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Deutsch", englishDescription: "German", tra
gravatar_profile_link: "Gravatar Profil"
play_level:
level_load_error: "Level konnte nicht geladen werden."
level_load_error: "Level konnte nicht geladen werden: "
done: "Fertig"
grid: "Raster"
customize_wizard: "Bearbeite den Zauberer"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "ελληνικά", englishDescription: "Gre
gravatar_profile_link: "Πλήρη προφίλ \"Gravatar\""
play_level:
level_load_error: "Το επίπεδο δεν μπόρεσε να φορτωθεί"
level_load_error: "Το επίπεδο δεν μπόρεσε να φορτωθεί: "
done: "Έτοιμο"
grid: "Πλέγμα"
customize_wizard: "Προσαρμόστε τον Μάγο"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "English (UK)", englishDescription: "English
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "English (US)", englishDescription: "English
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "English", englishDescription: "English", tr
gravatar_profile_link: "Full Gravatar Profile"
play_level:
level_load_error: "Level could not be loaded."
level_load_error: "Level could not be loaded: "
done: "Done"
grid: "Grid"
customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "español (América Latina)", englishDescrip
gravatar_profile_link: "Perfil Gravatar Completo"
play_level:
level_load_error: "El nivel no puede ser cargado."
level_load_error: "El nivel no puede ser cargado: "
done: "Listo"
grid: "Cuadricula"
customize_wizard: "Personalizar Hechicero"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "español (ES)", englishDescription: "Spanis
gravatar_profile_link: "Perfil de Gravatar completo"
play_level:
level_load_error: "No se pudo cargar el nivel."
level_load_error: "No se pudo cargar el nivel: "
done: "Hecho"
grid: "Cuadrícrula"
customize_wizard: "Personalizar Mago"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "español", englishDescription: "Spanish", t
gravatar_profile_link: "Prefil de Gravatar completo"
play_level:
level_load_error: "No se pudo cargar el nivel."
level_load_error: "No se pudo cargar el nivel: "
done: "Hecho"
grid: "Cuadrícrula"
customize_wizard: "Personalizar Mago"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "فارسی", englishDescription: "Persian",
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "suomi", englishDescription: "Finnish", tran
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "français", englishDescription: "French", t
gravatar_profile_link: "Profil Gravatar complet"
play_level:
level_load_error: "Le niveau ne peut pas être chargé."
level_load_error: "Le niveau ne peut pas être chargé: "
done: "Fait"
grid: "Grille"
customize_wizard: "Personnaliser le magicien"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "עברית", englishDescription: "Hebrew",
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "मानक हिन्दी", englishDe
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "magyar", englishDescription: "Hungarian", t
# gravatar_profile_link: "Full Gravatar Profile"
play_level:
level_load_error: "A pályát nem sikerült betölteni."
level_load_error: "A pályát nem sikerült betölteni: "
done: "Kész"
grid: "Rács"
customize_wizard: "Varázsló testreszabása"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Bahasa Indonesia", englishDescription: "Ind
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "italiano", englishDescription: "Italian", t
gravatar_profile_link: "Profilo Gravatar completo"
play_level:
level_load_error: "Il livello non può essere caricato."
level_load_error: "Il livello non può essere caricato: "
done: "Fatto"
grid: "Griglia"
customize_wizard: "Personalizza stregone"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "日本語", englishDescription: "Japanese",
# gravatar_profile_link: "Full Gravatar Profile"
play_level:
level_load_error: "レベルがロード出来ませんでした"
level_load_error: "レベルがロード出来ませんでした: "
done: "完了"
grid: "グリッド"
customize_wizard: "魔法使いの設定"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "한국어", englishDescription: "Korean", t
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Bahasa Melayu", englishDescription: "Bahasa
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Norsk Bokmål", englishDescription: "Norweg
gravatar_profile_link: "Full Gravatar Profil"
play_level:
level_load_error: "Nivået kunne ikke bli lastet."
level_load_error: "Nivået kunne ikke bli lastet: "
done: "Ferdig"
grid: "Grid"
customize_wizard: "Spesiallag Trollmann"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Nederlands", englishDescription: "Dutch", t
gravatar_profile_link: "Volledig Gravatar Profiel"
play_level:
level_load_error: "Level kon niet geladen worden."
level_load_error: "Level kon niet geladen worden: "
done: "Klaar"
grid: "Raster"
customize_wizard: "Pas Tovenaar aan"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "English (AU)", englishDescription: "English
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Norsk", englishDescription: "Norwegian", tr
gravatar_profile_link: "Full Gravatar Profil"
play_level:
level_load_error: "Nivået kunne ikke bli lastet."
level_load_error: "Nivået kunne ikke bli lastet: "
done: "Ferdig"
grid: "Grid"
customize_wizard: "Spesiallag Trollmann"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "język polski", englishDescription: "Polish
gravatar_profile_link: "Profil Gravatar"
play_level:
level_load_error: "Nie udało się wczytać poziomu."
level_load_error: "Nie udało się wczytać poziomu: "
done: "Zrobione"
grid: "Siatka"
customize_wizard: "Spersonalizuj Czarodzieja"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "português do Brasil", englishDescription:
gravatar_profile_link: "Perfil Completo do Gravatar"
play_level:
level_load_error: "O estágio não pôde ser carregado."
level_load_error: "O estágio não pôde ser carregado: "
done: "Pronto"
grid: "Grade"
customize_wizard: "Personalize o feiticeiro"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Português europeu", englishDescription: "P
gravatar_profile_link: "Perfil Gravatar completo"
play_level:
level_load_error: "O nível não pôde ser carregado."
level_load_error: "O nível não pôde ser carregado: "
done: "Concluir"
grid: "Grelha"
customize_wizard: "Personalizar Feiticeiro"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "português", englishDescription: "Portugues
gravatar_profile_link: "Perfil Completo do Gravatar"
play_level:
level_load_error: "O estágio não pôde ser carregado."
level_load_error: "O estágio não pôde ser carregado: "
done: "Pronto"
grid: "Grade"
customize_wizard: "Personalize o feiticeiro"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "limba română", englishDescription: "Roman
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "русский", englishDescription: "Russi
gravatar_profile_link: "Полный профиль на Gravatar"
play_level:
level_load_error: "Уровень не может быть загружен."
level_load_error: "Уровень не может быть загружен: "
done: "Готово"
grid: "Сетка"
customize_wizard: "Настройки волшебника"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "slovenčina", englishDescription: "Slovak",
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "slovenščina", englishDescription: "Sloven
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "српски", englishDescription: "Serbian
gravatar_profile_link: "Цео Граватар налог"
play_level:
level_load_error: "Ниво није могао бити учитан."
level_load_error: "Ниво није могао бити учитан: "
done: "Урађено"
grid: "Мрежа"
customize_wizard: "Прилагоди Чаробњака"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr
gravatar_profile_link: "Hela Gravatar profilen"
play_level:
level_load_error: "Nivån kunde inte laddas."
level_load_error: "Nivån kunde inte laddas: "
done: "Klar"
grid: "Rutnät"
customize_wizard: "Finjustera Trollkarl"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "ไทย", englishDescription: "Thai", tra
# gravatar_profile_link: "Full Gravatar Profile"
play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
done: "เสร็จสิ้น"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Türkçe", englishDescription: "Turkish", t
gravatar_profile_link: "Tam Gravatar Profili"
play_level:
level_load_error: "Seviye yüklenemedi"
level_load_error: "Seviye yüklenemedi: "
done: "Tamamdır"
grid: "Harita Bölmeleri"
customize_wizard: "Sihirbazı Düzenle"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "українська мова", englishDesc
gravatar_profile_link: "Повний профіль Gravatar"
play_level:
level_load_error: "Неможливо завантажити рівень."
level_load_error: "Неможливо завантажити рівень: "
done: "Готово"
grid: "Решітка"
customize_wizard: "Налаштування персонажа"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "اُردُو", englishDescription: "Urdu",
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "Tiếng Việt", englishDescription: "Vietn
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "简体中文", englishDescription: "Chinese
gravatar_profile_link: "完善 Gravatar 资料"
play_level:
level_load_error: "关卡不能载入"
level_load_error: "关卡不能载入: "
done: "完成"
grid: "格子"
customize_wizard: "自定义向导"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "繁体中文", englishDescription: "Chinese
gravatar_profile_link: "完善 Gravatar 資料"
play_level:
level_load_error: "載入關卡時發生錯誤"
level_load_error: "載入關卡時發生錯誤: "
done: "完成"
grid: "格子"
customize_wizard: "自定義巫師"

View file

@ -158,7 +158,7 @@ module.exports = nativeDescription: "中文", englishDescription: "Chinese", tra
# gravatar_profile_link: "Full Gravatar Profile"
# play_level:
# level_load_error: "Level could not be loaded."
# level_load_error: "Level could not be loaded: "
# done: "Done"
# grid: "Grid"
# customize_wizard: "Customize Wizard"

View file

@ -38,3 +38,6 @@
color: black
text-shadow: 0 1px 0 white
.alert-warning h2
color: black
text-align: center

View file

@ -3,6 +3,10 @@
left: 10px
top: 42px
background-color: rgba(200,200,200,0.8)
&.brighter
background-color: rgba(200,200,200,1.0)
border: black
padding: 5px 7px 5px 5px
box-sizing: border-box

View file

@ -0,0 +1,2 @@
#docs-modal .modal-dialog
width: 800px

View file

@ -2,6 +2,18 @@ extends /templates/base
block content
h3 Espionage mode
h5 Please enter the email/username of the person you want to spy on
.form
.form-group
label.control-label Email
input#user-email
.form-group
label.control-label Username
input#user-username
button.btn.btn-primary.btn-large#enter-espionage-mode 007
h3(data-i18n="admin.av_title") Admin Views
h4(data-i18n="admin.av_entities_sub_title") Entities

View file

@ -2,6 +2,12 @@ extends /templates/base
block content
if notFound
div(class="alert alert-warning")
h2
span(data-i18n="play_level.level_load_error") Level could not be loaded:
| #{notFound}
h1(data-i18n="play.choose_your_level") Choose Your Level
p
span(data-i18n="play.adventurer_prefix") You can jump to any level below, or discuss the levels on

View file

@ -7,7 +7,7 @@ block content
!{description}
if !me.get('anonymous')
a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
//a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
a(href="/play/level/ladder-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial

View file

@ -1,12 +1,12 @@
h4.home
a(href="/")
a(href=homeLink || "/")
i.icon-home.icon-white
span(data-i18n="play_level.home") Home
h4.title #{worldName}
button.btn.btn-xs.btn-inverse.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide
button.btn.btn-xs.btn-success.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide
if ladderGame
button.btn.btn-xs.btn-inverse.banner#multiplayer-button(title="Leaderboard", data-i18n="play_level.leaderboard") Leaderboard

View file

@ -38,12 +38,13 @@ class LiveEditingMarkup extends TreemaNode.nodeMap.ace
url: InkBlob.url
filename: InkBlob.filename
mimetype: InkBlob.mimetype
description: ''
createdFor: []
path: @settings.filePath
@uploadingPath = [@settings.filePath, InkBlob.filename].join('/')
$.ajax('/file', { type: 'POST', data: body, success: @onFileUploaded })
onFileUploaded: (e) =>
@editor.insert "![#{e.metadata.name}](/file/#{e._id})"
@editor.insert "![#{e.metadata.name}](/file/#{@uploadingPath})"
onEditorChange: =>
@saveChanges()

View file

@ -1,6 +1,35 @@
{backboneFailure, genericFailure} = require 'lib/errors'
View = require 'views/kinds/RootView'
template = require 'templates/admin'
storage = require 'lib/storage'
module.exports = class AdminView extends View
id: "admin-view"
template: template
events:
'click #enter-espionage-mode': 'enterEspionageMode'
enterEspionageMode: ->
userEmail = $("#user-email").val().toLowerCase()
username = $("#user-username").val().toLowerCase()
userIdentifier = userEmail || username
postData =
usernameLower: username
emailLower: userEmail
$.ajax
type: "POST",
url: "/auth/spy"
data: postData
success: @espionageSuccess
error: @espionageFailure
espionageSuccess: (model) ->
storage.save('whoami',model)
window.location.reload()
espionageFailure: (jqxhr, status,error)->
console.log "There was an error entering espionage mode: #{error}"

View file

@ -35,6 +35,7 @@ module.exports = class ArticleEditView extends View
data = $.extend(true, {}, @article.attributes)
options =
data: data
filePath: "db/thang.type/#{@article.get('original')}"
schema: Article.schema.attributes
callbacks:
change: @pushChangesToPreview

View file

@ -144,6 +144,7 @@ module.exports = class ThangsTabView extends View
@surface.playing = false
@surface.setWorld @world
@surface.camera.zoomTo({x:262, y:-164}, 1.66, 0)
@surface.camera.dragDisabled = true
destroy: ->
@selectAddThangType null

View file

@ -47,12 +47,17 @@ module.exports = class ControlBarView extends View
text += " (#{numPlayers})" if numPlayers > 1
$('#multiplayer-button', @$el).text(text)
getRenderData: (context={}) ->
super context
context.worldName = @worldName
context.multiplayerEnabled = @session.get('multiplayer')
context.ladderGame = @ladderGame
context
getRenderData: (c={}) ->
super c
c.worldName = @worldName
c.multiplayerEnabled = @session.get('multiplayer')
c.ladderGame = @ladderGame
c.homeLink = "/"
levelID = @level.get('slug')
if levelID in ["project-dota", "brawlwood", "ladder-tutorial"]
levelID = 'project-dota' if levelID is 'ladder-tutorial'
c.homeLink = "/play/ladder/" + levelID
c
showGuideModal: ->
options = {docs: @level.get('documentation'), supermodel: @supermodel}

View file

@ -14,6 +14,8 @@ module.exports = class GoalsView extends View
subscriptions:
'goal-manager:new-goal-states': 'onNewGoalStates'
'level-set-letterbox': 'onSetLetterbox'
'surface:playback-restarted': 'onSurfacePlaybackRestarted'
'surface:playback-ended': 'onSurfacePlaybackEnded'
events:
'click': 'toggleCollapse'
@ -48,6 +50,12 @@ module.exports = class GoalsView extends View
goals.push goal
@$el.removeClass('secret') if goals.length > 0
onSurfacePlaybackRestarted: ->
@$el.removeClass 'brighter'
onSurfacePlaybackEnded: ->
@$el.addClass 'brighter'
render: ->
super()
@$el.addClass('secret').addClass('expanded')

View file

@ -66,8 +66,9 @@ module.exports = class HUDView extends View
@clearSpeaker()
onNewWorld: (e) ->
hadThang = @thang
@thang = e.world.thangMap[@thang.id] if @thang
if not @thang
if hadThang and not @thang
@setThang null, null
setThang: (thang, thangType) ->
@ -269,7 +270,7 @@ module.exports = class HUDView extends View
if prop is "rotation"
return (val * 180 / Math.PI).toFixed(0) + "˚"
if typeof val is 'number'
if Math.round(val) == val then return val.toFixed(0) # int
if Math.round(val) == val or prop is 'gold' then return val.toFixed(0) # int
if -10 < val < 10 then return val.toFixed(2)
if -100 < val < 100 then return val.toFixed(1)
return val.toFixed(0)
@ -341,4 +342,5 @@ module.exports = class HUDView extends View
@addMoreMessage = null
@animateEnterButton = null
clearInterval(@messageInterval) if @messageInterval
clearTimeout @hintNextSelectionTimeout if @hintNextSelectionTimeout
super()

View file

@ -6,6 +6,7 @@ Article = require 'models/Article'
module.exports = class DocsModal extends View
template: template
id: 'docs-modal'
shortcuts:
'enter': 'hide'

View file

@ -82,11 +82,20 @@ module.exports = class DebugView extends View
else
@$el.hide()
if @variableChain?.length is 2
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: @variableChain[1], owner: @variableChain[0]
clearTimeout @hoveredPropertyTimeout if @hoveredPropertyTimeout
@hoveredPropertyTimeout = _.delay @notifyPropertyHovered, 500
else
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: null
@notifyPropertyHovered()
@updateMarker()
notifyPropertyHovered: =>
clearTimeout @hoveredPropertyTimeout if @hoveredPropertyTimeout
@hoveredPropertyTimeout = null
oldHoveredProperty = @hoveredProperty
@hoveredProperty = if @variableChain?.length is 2 then owner: @variableChain[0], property: @variableChain[1] else {}
unless _.isEqual oldHoveredProperty, @hoveredProperty
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', @hoveredProperty
updateMarker: ->
if @marker
@ace.getSession().removeMarker @marker

View file

@ -97,8 +97,7 @@ module.exports = class PlayLevelView extends View
localStorage["lastLevel"] = @levelID
onLevelLoadError: (e) =>
msg = $.i18n.t('play_level.level_load_error', defaultValue: "Level could not be loaded.")
@$el.html('<div class="alert">' + msg + '</div>')
application.router.navigate "/play?not_found=#{@levelID}", {trigger: true}
setLevel: (@level, @supermodel) ->
@god?.level = @level.serialize @supermodel
@ -136,7 +135,7 @@ module.exports = class PlayLevelView extends View
team = @getQueryVariable("team") ? @world.teamForPlayer(0)
opponentSpells = []
for spellTeam, spells of @session.get('teamSpells') or {}
for spellTeam, spells of @session.get('teamSpells') ? otherSession?.get('teamSpells') ? {}
continue if spellTeam is team or not team
opponentSpells = opponentSpells.concat spells
@ -144,8 +143,10 @@ module.exports = class PlayLevelView extends View
opponentCode = otherSession?.get('submittedCode') or {}
myCode = @session.get('code') or {}
for spell in opponentSpells
c = opponentCode[spell]
if c then myCode[spell] = c else delete myCode[spell]
[thang, spell] = spell.split '/'
c = opponentCode[thang]?[spell]
myCode[thang] ?= {}
if c then myCode[thang][spell] = c else delete myCode[thang][spell]
@session.set('code', myCode)
if @session.get('multiplayer') and otherSession?
# For now, ladderGame will disallow multiplayer, because session code combining doesn't play nice yet.
@ -393,7 +394,7 @@ module.exports = class PlayLevelView extends View
register: ->
@bus = LevelBus.get(@levelID, @session.id)
@bus.setSession(@session)
@bus.setTeamSpellMap @tome.teamSpellMap
@bus.setSpells @tome.spells
@bus.connect() if @session.get('multiplayer')
onSessionWillSave: (e) ->
@ -423,7 +424,7 @@ module.exports = class PlayLevelView extends View
AudioPlayer.preloadSoundReference sound
destroy: ->
@supermodel.off 'error', @onLevelLoadError
@supermodel?.off 'error', @onLevelLoadError
@levelLoader?.off 'loaded-all', @onLevelLoaderLoaded
@levelLoader?.destroy()
@surface?.destroy()
@ -436,7 +437,7 @@ module.exports = class PlayLevelView extends View
@bus?.destroy()
#@instance.save() unless @instance.loading
console.profileEnd?() if PROFILE_ME
@session.off 'change:multiplayer', @onMultiplayerChanged, @
@session?.off 'change:multiplayer', @onMultiplayerChanged, @
@onLevelLoadError = null
@onLevelLoaderLoaded = null
@onSupermodelLoadedOne = null

View file

@ -8,7 +8,7 @@ module.exports = class PlayView extends View
getRenderData: (context={}) ->
context = super(context)
context.home = true
context.notFound = @getQueryVariable 'not_found'
tutorials = [
{
name: 'Rescue Mission'

View file

@ -62,7 +62,6 @@
"sendwithus": "2.0.x",
"aws-sdk":"~2.0.0",
"bayesian-battle":"0.0.x",
"hiredis":"",
"redis": ""
},
"devDependencies": {
@ -77,7 +76,7 @@
"clean-css-brunch": "> 1.0 < 1.8",
"auto-reload-brunch": "> 1.0 < 1.8",
"brunch": "~1.7.4",
"jasmine-node": "1.12.x",
"jasmine-node": "1.13.x",
"nodemon": "0.7.5",
"marked": "0.2.x",
"telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",

View file

@ -16,7 +16,7 @@ module.exports = class Handler
# subclasses should override these methods
hasAccess: (req) -> true
hasAccessToDocument: (req, document, method=null) ->
return true if req.user.isAdmin()
return true if req.user?.isAdmin()
if @modelClass.schema.uses_coco_permissions
return document.hasPermissionsForMethod(req.user, method or req.method)
return true
@ -32,7 +32,7 @@ module.exports = class Handler
# can only edit permissions if this is a brand new property,
# or you are an owner of the old one
isOwner = document.getAccessForUserObjectId(req.user._id) is 'owner'
if isBrandNew or isOwner or req.user.isAdmin()
if isBrandNew or isOwner or req.user?.isAdmin()
props.push 'permissions'
if @modelClass.schema.uses_coco_versions
@ -57,7 +57,7 @@ module.exports = class Handler
# generic handlers
get: (req, res) ->
# by default, ordinary users never get unfettered access to the database
return @sendUnauthorizedError(res) unless req.user.isAdmin()
return @sendUnauthorizedError(res) unless req.user?.isAdmin()
# admins can send any sort of query down the wire, though
conditions = JSON.parse(req.query.conditions || '[]')
@ -97,7 +97,7 @@ module.exports = class Handler
term = req.query.term
matchedObjects = []
filters = [{filter: {index: true}}]
if @modelClass.schema.uses_coco_permissions
if @modelClass.schema.uses_coco_permissions and req.user
filters.push {filter: {index: req.user.get('id')}}
for filter in filters
callback = (err, results) =>

View file

@ -39,6 +39,7 @@ LevelHandler = class LevelHandler extends Handler
callback err, level
getSession: (req, res, id) ->
return @sendNotFoundError(res) unless req.user
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
sessionQuery =
level:
@ -150,6 +151,7 @@ LevelHandler = class LevelHandler extends Handler
req.query.limit = parseInt(req.query.limit) ? 20
getFeedback: (req, res, id) ->
return @sendNotFoundError(res) unless req.user
@fetchLevelByIDAndHandleErrors id, req, res, (err, level) =>
feedbackQuery =
creator: mongoose.Types.ObjectId(req.user.id.toString())

View file

@ -147,7 +147,9 @@ ScriptSchema = c.object {
eventPrereqs: c.array {title: "Event Checks", description: "Logical checks on the event for this script to trigger.", format:'event-prereqs'}, EventPrereqSchema
repeats: {title: "Repeats", description: "Whether this script can trigger more than once during a level.", type: 'boolean', "default": false}
scriptPrereqs: c.array {title: "Happens After", description: "Scripts that need to fire first."},
c.shortString(title: "ID", description: "A unique ID of a script that must have triggered before the parent script can trigger.")
c.shortString(title: "ID", description: "A unique ID of a script.")
notAfter: c.array {title: "Not After", description: "Do not run this script if any of these scripts have run."},
c.shortString(title: "ID", description: "A unique ID of a script.")
noteChain: c.array {title: "Actions", description: "A list of things that happen when this script triggers."}, NoteGroupSchema
LevelThangSchema = c.object {

View file

@ -139,6 +139,13 @@ _.extend LevelSessionSchema.properties,
submittedCode:
type: 'object'
numberOfWinsAndTies:
type: 'number'
default: 0
numberOfLosses:
type: 'number'
default: 0
matches:
type: 'array'

View file

@ -12,7 +12,7 @@ TaskLog = require './task/ScoringTask'
bayes = new (require 'bayesian-battle')()
scoringTaskQueue = undefined
scoringTaskTimeoutInSeconds = 120
scoringTaskTimeoutInSeconds = 180
module.exports.setup = (app) -> connectToScoringQueue()
@ -24,24 +24,27 @@ connectToScoringQueue = ->
scoringTaskQueue = data
log.info "Connected to scoring task queue!"
module.exports.addPairwiseTaskToQueue = (req, res) ->
module.exports.addPairwiseTaskToQueueFromRequest = (req, res) ->
taskPair = req.body.sessions
#unless isUserAdmin req then return errors.forbidden res, "You do not have the permissions to submit that game to the leaderboard"
#fetch both sessions
addPairwiseTaskToQueue req.body.sessions (err, success) ->
if err? then return errors.serverError res, "There was an error adding pairwise tasks: #{err}"
sendResponseObject req, res, {"message":"All task pairs were succesfully sent to the queue"}
addPairwiseTaskToQueue = (taskPair, cb) ->
LevelSession.findOne(_id:taskPair[0]).lean().exec (err, firstSession) =>
if err? then return errors.serverError res, "There was an error fetching the first session in the pair"
if err? then return cb err, false
LevelSession.find(_id:taskPair[1]).exec (err, secondSession) =>
if err? then return errors.serverError res, "There was an error fetching the second session"
if err? then return cb err, false
try
taskPairs = generateTaskPairs(secondSession, firstSession)
catch e
if e then return errors.serverError res, "There was an error generating the task pairs"
sendEachTaskPairToTheQueue taskPairs, (taskPairError) ->
if taskPairError? then return errors.serverError res, "There was an error sending the task pairs to the queue"
if e then return cb e, false
sendResponseObject req, res, {"message":"All task pairs were succesfully sent to the queue"}
sendEachTaskPairToTheQueue taskPairs, (taskPairError) ->
if taskPairError? then return cb taskPairError,false
cb null, true
module.exports.createNewTask = (req, res) ->
requestSessionID = req.body.session
@ -56,8 +59,8 @@ module.exports.createNewTask = (req, res) ->
updateSessionToSubmit sessionToSubmit, (err, data) ->
if err? then return errors.serverError res, "There was an error updating the session"
fetchSessionsToRankAgainst (err, sessionsToRankAgainst) ->
opposingTeam = calculateOpposingTeam(sessionToSubmit.team)
fetchInitialSessionsToRankAgainst opposingTeam, (err, sessionsToRankAgainst) ->
if err? then return errors.serverError res, "There was an error fetching the sessions to rank against"
taskPairs = generateTaskPairs(sessionsToRankAgainst, sessionToSubmit)
@ -114,9 +117,102 @@ module.exports.processTaskResult = (req, res) ->
addMatchToSessions clientResponseObject, newScoresObject, (err, data) ->
if err? then return errors.serverError res, "There was an error updating the sessions with the match! #{JSON.stringify err}"
console.log "Sending response object"
sendResponseObject req, res, {"message":"The scores were updated successfully!"}
originalSessionID = clientResponseObject.originalSessionID
originalSessionTeam = clientResponseObject.originalSessionTeam
originalSessionRank = parseInt clientResponseObject.originalSessionRank
determineIfSessionShouldContinueAndUpdateLog originalSessionID, originalSessionRank, (err, sessionShouldContinue) ->
if err? then return errors.serverError res, "There was an error determining if the session should continue, #{err}"
if sessionShouldContinue
opposingTeam = calculateOpposingTeam(originalSessionTeam)
opponentID = _.pull(_.keys(newScoresObject), originalSessionID)
sessionNewScore = newScoresObject[originalSessionID].totalScore
opponentNewScore = newScoresObject[opponentID].totalScore
findNearestBetterSessionID sessionNewScore, opponentNewScore, opponentID ,opposingTeam, (err, opponentSessionID) ->
if err? then return errors.serverError res, "There was an error finding the nearest sessionID!"
unless opponentSessionID then return sendResponseObject req, res, {"message":"There were no more games to rank(game is at top!"}
addPairwiseTaskToQueue [originalSessionID, opponentSessionID], (err, success) ->
if err? then return errors.serverError res, "There was an error sending the pairwise tasks to the queue!"
sendResponseObject req, res, {"message":"The scores were updated successfully and more games were sent to the queue!"}
else
console.log "Player lost, achieved rank #{originalSessionRank}"
sendResponseObject req, res, {"message":"The scores were updated successfully, person lost so no more games are being inserted!"}
determineIfSessionShouldContinueAndUpdateLog = (sessionID, sessionRank, cb) ->
queryParameters =
_id: sessionID
updateParameters =
"$inc": {}
if sessionRank is 0
updateParameters["$inc"] = {numberOfWinsAndTies: 1}
else
updateParameters["$inc"] = {numberOfLosses: 1}
LevelSession.findOneAndUpdate queryParameters, updateParameters,{select: 'numberOfWinsAndTies numberOfLosses'}, (err, updatedSession) ->
if err? then return cb err, updatedSession
updatedSession = updatedSession.toObject()
totalNumberOfGamesPlayed = updatedSession.numberOfWinsAndTies + updatedSession.numberOfLosses
if totalNumberOfGamesPlayed < 5
console.log "Number of games played is less than 5, continuing..."
cb null, true
else if totalNumberOfGamesPlayed > 15
console.log "Too many games played, ending..."
cb null, false
else
ratio = (updatedSession.numberOfLosses - 5) / (totalNumberOfGamesPlayed)
if ratio > 0.66
cb null, false
console.log "Ratio(#{ratio}) is bad, ending simulation"
else
console.log "Ratio(#{ratio}) is good, so continuing simulations"
cb null, true
findNearestBetterSessionID = (sessionTotalScore, opponentSessionTotalScore, opponentSessionID, opposingTeam, cb) ->
queryParameters =
totalScore:
$gt:opponentSessionTotalScore + 0.5
_id:
$ne: opponentSessionID
levelID: "project-dota"
submitted: true
submittedCode:
$exists: true
team: opposingTeam
limitNumber = 1
sortParameters =
totalScore: 1
selectString = '_id totalScore'
query = LevelSession.findOne(queryParameters)
.sort(sortParameters)
.limit(limitNumber)
.select(selectString)
.lean()
console.log "Finding session with score near #{opponentSessionTotalScore}"
query.exec (err, session) ->
if err? then return cb err, session
unless session then return cb err, null
console.log "Found session with score #{session.totalScore}"
cb err, session._id
calculateOpposingTeam = (sessionTeam) ->
teams = ['ogres','humans']
opposingTeams = _.pull teams, sessionTeam
return opposingTeams[0]
validatePermissions = (req, sessionID, callback) ->
if isUserAnonymous req then return callback null, false
if isUserAdmin req then return callback null, true
@ -177,15 +273,30 @@ updateSessionToSubmit = (sessionToUpdate, callback) ->
meanStrength: 25
standardDeviation: 25/3
totalScore: 10
numberOfWinsAndTies: 0
numberOfLosses: 0
LevelSession.update {_id: sessionToUpdate._id}, sessionUpdateObject, callback
fetchSessionsToRankAgainst = (callback) ->
submittedSessionsQuery =
fetchInitialSessionsToRankAgainst = (opposingTeam, callback) ->
console.log "Fetching sessions to rank against for opposing team #{opposingTeam}"
findParameters =
levelID: "project-dota"
submitted: true
submittedCode:
$exists: true
LevelSession.find submittedSessionsQuery, callback
team: opposingTeam
sortParameters =
totalScore: 1
limitNumber = 1
query = LevelSession.find(findParameters)
.sort(sortParameters)
.limit(limitNumber)
query.exec callback
generateTaskPairs = (submittedSessions, sessionToScore) ->
taskPairs = []

View file

@ -28,7 +28,30 @@ module.exports.setup = (app) ->
return done(null, user)
)
))
app.post '/auth/spy', (req, res, next) ->
if req?.user?.isAdmin()
username = req.body.usernameLower
emailLower = req.body.emailLower
if emailLower
query = {"emailLower":emailLower}
else if username
query = {"nameLower":username}
else
return errors.badInput res, "You need to supply one of emailLower or username"
User.findOne query, (err, user) ->
if err? then return errors.serverError res, "There was an error finding the specified user"
unless user then return errors.badInput res, "The specified user couldn't be found"
req.logIn user, (err) ->
if err? then return errors.serverError res, "There was an error logging in with the specified"
res.send(UserHandler.formatEntity(req, user))
return res.end()
else
return errors.unauthorized res, "You must be an admin to enter espionage mode"
app.post('/auth/login', (req, res, next) ->
authentication.authenticate('local', (err, user, info) ->
return next(err) if err

View file

@ -4,6 +4,7 @@ mail = require '../commons/mail'
module.exports.setup = (app) ->
app.post '/contact', (req, res) ->
return res.end() unless req.user
log.info "Sending mail from #{req.body.email} saying #{req.body.message}"
if config.isProduction
options = createMailOptions req.body.email, req.body.message, req.user

View file

@ -11,6 +11,7 @@ module.exports.setup = (app) ->
parts = module.split('/')
module = parts[0]
return getSchema(req, res, module) if parts[1] is 'schema'
return errors.unauthorized(res, 'Must have an identity to do anything with the db.') unless req.user
try
moduleName = module.replace '.', '_'

View file

@ -69,7 +69,7 @@ postFileSchema =
required: ['filename', 'mimetype', 'path']
filePost = (req, res) ->
return errors.forbidden(res) unless req.user.isAdmin()
return errors.forbidden(res) unless req.user?.isAdmin()
options = req.body
tv4 = require('tv4').tv4
valid = tv4.validate(options, postFileSchema)

View file

@ -31,7 +31,7 @@ UserHandler = class UserHandler extends Handler
return null unless document?
obj = document.toObject()
delete obj[prop] for prop in serverProperties
includePrivates = req.user and (req.user.isAdmin() or req.user._id.equals(document._id))
includePrivates = req.user and (req.user?.isAdmin() or req.user?._id.equals(document._id))
delete obj[prop] for prop in privateProperties unless includePrivates
# emailHash is used by gravatar
@ -105,7 +105,7 @@ UserHandler = class UserHandler extends Handler
]
getById: (req, res, id) ->
if req.user and req.user._id.equals(id)
if req.user?._id.equals(id)
return @sendSuccess(res, @formatEntity(req, req.user))
super(req, res, id)
@ -132,14 +132,15 @@ UserHandler = class UserHandler extends Handler
post: (req, res) ->
return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
return @sendBadInputError(res, 'Must have an anonymous user to post with.') unless req.user
return @sendBadInputError(res, 'Existing users cannot create new ones.') unless req.user.get('anonymous')
req.body._id = req.user._id if req.user.get('anonymous')
@put(req, res)
hasAccessToDocument: (req, document) ->
if req.route.method in ['put', 'post', 'patch']
return true if req.user.isAdmin()
return req.user._id.equals(document._id)
return true if req.user?.isAdmin()
return req.user?._id.equals(document._id)
return true
getByRelationship: (req, res, args...) ->
@ -149,6 +150,7 @@ UserHandler = class UserHandler extends Handler
return @sendNotFoundError(res)
agreeToCLA: (req, res) ->
return @sendUnauthorizedError(res) unless req.user
doc =
user: req.user._id+''
email: req.user.get 'email'

View file

@ -1,6 +1,9 @@
# import this at the top of every file so we're not juggling connections
# and common libraries are available
console.log 'IT BEGINS'
GLOBAL._ = require('lodash')
_.str = require('underscore.string')
_.mixin(_.str.exports())
@ -71,20 +74,22 @@ unittest.getUser = (email, password, done, force) ->
return done(unittest.users[email]) if unittest.users[email] and not force
request = require 'request'
request.post getURL('/auth/logout'), ->
req = request.post(getURL('/db/user'), (err, response, body) ->
throw err if err
User.findOne({email:email}).exec((err, user) ->
if password is '80yqxpb38j'
user.set('permissions', [ 'admin' ])
user.save (err) ->
request.get getURL('/auth/whoami'), ->
req = request.post(getURL('/db/user'), (err, response, body) ->
throw err if err
User.findOne({email:email}).exec((err, user) ->
if password is '80yqxpb38j'
user.set('permissions', [ 'admin' ])
user.save (err) ->
wrapUpGetUser(email, user, done)
else
wrapUpGetUser(email, user, done)
else
wrapUpGetUser(email, user, done)
)
)
)
form = req.form()
form.append('email', email)
form.append('password', password)
form = req.form()
form.append('email', email)
form.append('password', password)
wrapUpGetUser = (email, user, done) ->
unittest.users[email] = user

View file

@ -17,8 +17,9 @@ describe '/auth/login', ->
it 'clears Users first', (done) ->
User.remove {}, (err) ->
throw err if err
done()
request.get getURL('/auth/whoami'), ->
throw err if err
done()
it 'finds no user', (done) ->
req = request.post(urlLogin, (error, response) ->
@ -92,9 +93,10 @@ describe '/auth/reset', ->
form = req.form()
form.append('email', 'unknow')
it 'reset user password', (done) ->
it 'resets user password', (done) ->
req = request.post(urlReset, (error, response) ->
expect(response).toBeDefined()
console.log 'status code is', response.statusCode
expect(response.statusCode).toBe(200)
expect(response.body).toBeDefined()
passwordReset = response.body

View file

@ -54,18 +54,16 @@ describe 'POST /db/user', ->
describe 'PUT /db/user', ->
it 'denies requests without any data', (done) ->
req = request.post getURL('/auth/logout'),
(err, res) ->
expect(res.statusCode).toBe(200)
req = request.put getURL(urlUser),
(err, res) ->
expect(res.statusCode).toBe(422)
expect(res.body).toBe('No input.')
done()
it 'logs in as normal joe', (done) ->
loginJoe -> done()
request.post getURL('/auth/logout'),
loginJoe -> done()
it 'denies requests without any data', (done) ->
request.put getURL(urlUser),
(err, res) ->
expect(res.statusCode).toBe(422)
expect(res.body).toBe('No input.')
done()
it 'denies requests to edit someone who is not joe', (done) ->
unittest.getAdmin (admin) ->