Merge pull request #415 from codecombat/master

Merge master into production
This commit is contained in:
Michael Schmatz 2014-02-24 17:58:02 -08:00
commit ce56e5df03
50 changed files with 543 additions and 330 deletions

View file

@ -1,6 +1,7 @@
app = require 'application'
auth = require 'lib/auth'
$ ->
init = ->
app.initialize()
Backbone.history.start({ pushState: true })
handleNormalUrls()
@ -9,6 +10,15 @@ $ ->
treemaExt.setup()
filepicker.setKey('AvlkNoldcTOU4PvKi2Xm7z')
$ ->
# Make sure we're "logged in" first.
if auth.me.id
init()
else
Backbone.Mediator.subscribeOnce 'me:synced', init
window.init = init
handleNormalUrls = ->
# http://artsy.github.com/blog/2012/06/25/replacing-hashbang-routes-with-pushstate/
$(document).on "click", "a[href^='/']", (event) ->

View file

@ -141,8 +141,8 @@ module.exports = class SpriteBuilder
return unless shapes.length
colors = @initColorMap(shapes)
@adjustHuesForColorMap(colors, config.hue)
@adjustValueForColorMap(colors, 1, config.lightness)
@adjustValueForColorMap(colors, 2, config.saturation)
@adjustValueForColorMap(colors, 1, config.saturation)
@adjustValueForColorMap(colors, 2, config.lightness)
@applyColorMap(shapes, colors)
initColorMap: (shapes) ->

View file

@ -0,0 +1,48 @@
CocoClass = require 'lib/CocoClass'
module.exports = class CastingScreen extends CocoClass
subscriptions:
'tome:cast-spells': 'onCastingBegins'
'god:new-world-created': 'onCastingEnds'
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()
onCastingBegins: ->
@show()
onCastingEnds: ->
@hide()
toString: -> "<CastingScreen>"
build: ->
@dimLayer = new createjs.Container()
@dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
@dimLayer.layerIndex = -11
@dimLayer.addChild @dimScreen = new createjs.Shape()
@dimScreen.graphics.beginFill("rgba(0,0,0,0.5)").rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
@dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight
@dimLayer.alpha = 0
@layer.addChild @dimLayer
show: ->
return if @on
@on = true
@dimLayer.alpha = 0
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha:1}, 500)
hide: ->
return unless @on
@on = false
createjs.Tween.removeTweens @dimLayer
createjs.Tween.get(@dimLayer).to({alpha:0}, 500)

View file

@ -65,13 +65,13 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@age = 0
@displayObject = new createjs.Container()
if @thangType.get('actions')
@onThangTypeLoaded()
@setupSprite()
else
@stillLoading = true
@thangType.fetch()
@thangType.once 'sync', @onThangTypeLoaded, @
@thangType.once 'sync', @setupSprite, @
onThangTypeLoaded: ->
setupSprite: ->
@stillLoading = false
@actions = @thangType.getActions()
@buildFromSpriteSheet @buildSpriteSheet()
@ -101,6 +101,7 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
# temp, until these are re-exported with perspective
if @options.camera and @thangType.get('name') in ['Dungeon Floor', 'Indoor Floor', 'Grass', 'Goal Trigger', 'Obstacle']
sprite.scaleY *= @options.camera.y2x
@displayObject.removeChild(@imageObject) if @imageObject
@imageObject = sprite
@displayObject.addChild(sprite)
@addHealthBar()
@ -157,6 +158,14 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
@hiding = false
@updateAlpha()
stop: ->
@imageObject?.stop?()
mark.stop() for name, mark of @marks
play: ->
@imageObject?.play?()
mark.play() for name, mark of @marks
update: ->
# Gets the sprite to reflect what the current state of the thangs and surface are
return if @stillLoading
@ -197,8 +206,11 @@ module.exports = CocoSprite = class CocoSprite extends CocoClass
if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight
[@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height]
bounds = @imageObject.getBounds()
@imageObject.scaleX = @thang.width * Camera.PPM / bounds.width * @thangType.get('scale') ? 1
@imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height * @thangType.get('scale') ? 1
@imageObject.scaleX = @thang.width * Camera.PPM / bounds.width
@imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height
unless @thang.spriteName is 'Beam'
@imageObject.scaleX *= @thangType.get('scale') ? 1
@imageObject.scaleY *= @thangType.get('scale') ? 1
return
scaleX = if @getActionProp 'flipX' then -1 else 1
scaleY = if @getActionProp 'flipY' then -1 else 1

View file

@ -54,17 +54,19 @@ module.exports = class Label extends CocoClass
buildLabelOptions: ->
o = {}
st = {dialogue: 'D', say: 'S', name: 'N'}[@style]
o.marginX = {D: 5, S: 2, N: 3}[st]
o.marginY = {D: 6, S: 2, N: 3}[st]
o.marginX = {D: 5, S: 6, N: 3}[st]
o.marginY = {D: 6, S: 4, N: 3}[st]
o.fontWeight = {D: "bold", S: "bold", N: "bold"}[st]
o.shadow = {D: false, S: true, N: true}[st]
o.fontSize = {D: 25, S: 19, N: 14}[st]
o.shadowColor = {D: "#FFF", S: "#000", N: "#FFF"}[st]
o.fontSize = {D: 25, S: 12, N: 12}[st]
fontFamily = {D: "Arial", S: "Arial", N: "Arial"}[st]
o.fontDescriptor = "#{o.fontSize}px #{fontFamily}"
o.fontColor = {D: "#000", S: "#000", N: "#00a"}[st]
o.backgroundFillColor = {D: "white", S: "rgba(255, 255, 255, 0.2)", N: "rgba(255, 255, 255, 0.5)"}[st]
o.backgroundStrokeColor = {D: "black", S: "rgba(0, 0, 0, 0.2)", N: "rgba(0, 0, 0, 0.0)"}[st]
o.fontDescriptor = "#{o.fontWeight} #{o.fontSize}px #{fontFamily}"
o.fontColor = {D: "#000", S: "#FFF", N: "#00a"}[st]
o.backgroundFillColor = {D: "white", S: "rgba(0, 0, 0, 0.4)", N: "rgba(255, 255, 255, 0.5)"}[st]
o.backgroundStrokeColor = {D: "black", S: "rgba(0, 0, 0, .6)", N: "rgba(0, 0, 0, 0.0)"}[st]
o.backgroundStrokeStyle = {D: 2, S: 1, N: 1}[st]
o.backgroundBorderRadius = {D: 10, S: 5, N: 3}[st]
o.backgroundBorderRadius = {D: 10, S: 3, N: 3}[st]
o.layerPriority = {D: 10, S: 5, N: 5}[st]
maxWidth = {D: 300, S: 300, N: 180}[st]
maxWidth = Math.max @camera.canvasWidth / 2 - 100, maxWidth # Does this do anything?
@ -79,7 +81,7 @@ module.exports = class Label extends CocoClass
label.lineHeight = o.fontSize + 2
label.x = o.marginX
label.y = o.marginY
label.shadow = new createjs.Shadow "#FFF", 1, 1, 0 if o.shadow
label.shadow = new createjs.Shadow o.shadowColor, 1, 1, 0 if o.shadow
label.layerPriority = o.layerPriority
label.name = "Sprite Label - #{@style}"
o.textHeight = label.getMeasuredHeight()

View file

@ -48,7 +48,7 @@ module.exports = class Mark extends CocoClass
build: ->
unless @mark
if @name is 'bounds' then @buildBounds()
else if @name is 'shadow' then @buildRadius()
else if @name is 'shadow' then @buildShadow()
else if @name is 'debug' then @buildDebug()
else if @thangType then @buildSprite()
else console.error "Don't know how to build mark for", @name
@ -87,21 +87,31 @@ module.exports = class Mark extends CocoClass
@lastWidth = @sprite.thang.width
@lastHeight = @sprite.thang.height
buildRadius: ->
# TODO: make this not just a shadow
# TODO: draw boxes and ellipses for non-circular Thangs
diameter = @sprite.thangType.get('shadow') ? @sprite.thang?.width + 0.5
diameter *= Camera.PPM
buildShadow: ->
width = (@sprite.thang?.width ? 0) + 0.5
height = (@sprite.thang?.height ? 0) + 0.5
longest = Math.max width, height
actualLongest = @sprite.thangType.get('shadow') ? longest
width = width * actualLongest / longest
height = height * actualLongest / longest
width *= Camera.PPM
height *= Camera.PPM * @camera.y2x # TODO: doesn't work with rotation
@mark = new createjs.Shape()
@mark.mouseEnabled = false
@mark.graphics.beginFill "black"
@mark.graphics.drawEllipse 0, 0, diameter, diameter * @camera.y2x
if @sprite.thang.shape in ['ellipsoid', 'disc']
@mark.graphics.drawEllipse 0, 0, width, height
else
@mark.graphics.drawRect 0, 0, width, height
@mark.graphics.endFill()
@mark.regX = diameter / 2
@mark.regY = diameter / 2 * @camera.y2x
@mark.regX = width / 2
@mark.regY = height / 2
@mark.layerIndex = 10
#@mark.cache 0, 0, diameter, diameter # not actually faster than simple ellipse draw
buildRadius: ->
return # not implemented
buildDebug: ->
@mark = new createjs.Shape()
PX = 3
@ -152,7 +162,7 @@ module.exports = class Mark extends CocoClass
@mark.y += offset.y
updateRotation: ->
if @name is 'debug'
if @name is 'debug' or (@name is 'shadow' and @sprite.thang?.shape in ["rectangle", "box"])
@mark.rotation = @sprite.thang.rotation * 180 / Math.PI
updateScale: ->
@ -174,3 +184,6 @@ module.exports = class Mark extends CocoClass
@mark.scaleX = @mark.scaleY = Math.min 1, scale
if @name in ['selection', 'target', 'repair']
@mark.scaleY *= @camera.y2x # code applies perspective
stop: -> @markSprite?.stop()
play: -> @markSprite?.play()

View file

@ -20,6 +20,7 @@ module.exports = class SpriteBoss extends CocoClass
'level-lock-select': 'onSetLockSelect'
'level:restarted': 'onLevelRestarted'
'god:new-world-created': 'onNewWorld'
'tome:cast-spells': 'onCastSpells'
constructor: (@options) ->
super()
@ -205,6 +206,14 @@ module.exports = class SpriteBoss extends CocoClass
onNewWorld: (e) ->
@world = @options.world = e.world
sprite.imageObject.play() for thangID, sprite of @sprites
@selectionMark?.play()
@targetMark?.play()
onCastSpells: ->
sprite.imageObject.stop() for thangID, sprite of @sprites
@selectionMark?.stop()
@targetMark?.stop()
# Selection

View file

@ -8,6 +8,7 @@ CameraBorder = require './CameraBorder'
Layer = require './Layer'
Letterbox = require './Letterbox'
Dimmer = require './Dimmer'
CastingScreen = require './CastingScreen'
DebugDisplay = require './DebugDisplay'
CoordinateDisplay = require './CoordinateDisplay'
SpriteBoss = require './SpriteBoss'
@ -88,6 +89,7 @@ module.exports = Surface = class Surface extends CocoClass
@spriteBoss.destroy()
@chooser?.destroy()
@dimmer?.destroy()
@castingScreen?.destroy()
@stage.clear()
@musicPlayer?.destroy()
@stage.removeAllChildren()
@ -224,7 +226,7 @@ module.exports = Surface = class Surface extends CocoClass
@currentFrame = actualCurrentFrame
# TODO: are these needed, or perhaps do they duplicate things?
@spriteBoss.update()
@spriteBoss.update true
@onFrameChanged()
getCurrentFrame: ->
@ -299,11 +301,18 @@ module.exports = Surface = class Surface extends CocoClass
@lastFrame = @currentFrame
onCastSpells: (event) ->
@casting = true
@wasPlayingWhenCastingBegan = @playing
Backbone.Mediator.publish 'level-set-playing', { playing: false }
createjs.Tween.removeTweens(@surfaceLayer)
createjs.Tween.get(@surfaceLayer).to({alpha:0.9}, 1000, createjs.Ease.getPowOut(4.0))
onNewWorld: (event) ->
return unless event.world.name is @world.name
@casting = false
Backbone.Mediator.publish 'level-set-playing', { playing: @wasPlayingWhenCastingBegan }
fastForwardTo = null
if @playing
fastForwardTo = Math.min event.world.firstChangedFrame, @currentFrame
@ -340,6 +349,7 @@ module.exports = Surface = class Surface extends CocoClass
@surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds
@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
@stage.enableMouseOver(10)
@stage.addEventListener 'stagemousemove', @onMouseMove
@stage.addEventListener 'stagemousedown', @onMouseDown
@ -497,7 +507,7 @@ module.exports = Surface = class Surface extends CocoClass
updateState: (frameChanged) ->
# world state must have been restored in @updateSpriteSounds
@camera.updateZoom()
@spriteBoss.update frameChanged
@spriteBoss.update frameChanged unless @casting
@dimmer?.setSprites @spriteBoss.sprites
drawCurrentFrame: (e) ->
@ -508,6 +518,7 @@ module.exports = Surface = class Surface extends CocoClass
updatePaths: ->
return unless @options.paths
return if @casting
@hidePaths()
selectedThang = @spriteBoss.selectedSprite?.thang
return if @world.showPaths is 'never'

View file

@ -24,7 +24,7 @@ module.exports = class WizardSprite extends IndieSprite
constructor: (thangType, options) ->
if options?.isSelf
options.colorConfig = me.get('wizard')?.colorConfig or {}
options.colorConfig = _.cloneDeep(me.get('wizard')?.colorConfig) or {}
super thangType, options
@isSelf = options.isSelf
@targetPos = @thang.pos
@ -59,7 +59,12 @@ module.exports = class WizardSprite extends IndieSprite
onMeSynced: (e) ->
return unless @isSelf
@setNameLabel me.displayName() if @displayObject.visible # not if we hid the wiz
@setColorHue me.get('wizardColor1')
newColorConfig = me.get('wizard')?.colorConfig or {}
shouldUpdate = not _.isEqual(newColorConfig, @options.colorConfig)
@options.colorConfig = _.cloneDeep(newColorConfig)
if shouldUpdate
@setupSprite()
@playAction(@currentAction)
onSpriteSelected: (e) ->
return unless @isSelf

View file

@ -55,7 +55,7 @@ module.exports = class World
@thangMap[thang.id] = thang
thangDialogueSounds: ->
if @frames.length < @totalFrames then worldShouldBeOverBeforeGrabbingDialogue
if @frames.length < @totalFrames then throw new Error("World should be over before grabbing dialogue")
[sounds, seen] = [[], {}]
for frame in @frames
for thangID, state of frame.thangStateMap
@ -245,7 +245,7 @@ module.exports = class World
serialize: ->
# Code hotspot; optimize it
if @frames.length < @totalFrames then worldShouldBeOverBeforeSerialization
if @frames.length < @totalFrames then throw new Error("World Should Be Over Before Serialization")
[transferableObjects, nontransferableObjects] = [0, 0]
o = {name: @name, totalFrames: @totalFrames, maxTotalFrames: @maxTotalFrames, frameRate: @frameRate, dt: @dt, victory: @victory, userCodeMap: {}, trackedProperties: {}}
o.trackedProperties[prop] = @[prop] for prop in @trackedProperties or []

View file

@ -38,43 +38,3 @@
.form
max-width: 600px
#wizard-settings-tab-view
#color-settings
float: left
width: 600px
margin-left: 30px
canvas
float: left
border: 2px solid black
margin: 20px
.color-group
clear: both
padding-bottom: 10px
margin-bottom: 10px
border-bottom: 1px solid gray
.name-cell
float: left
width: 100px
padding-top: 2px
input
margin-right: 10px
position: relative
top: -3px
.checkbox-cell
float: left
width: 40px
.slider-cell
margin-bottom: 10px
float: left
width: 120px
.selector
width: 100px

View file

@ -0,0 +1,42 @@
#wizard-settings-view
h3#loading
text-align: center
#color-settings
float: left
width: 600px
margin-left: 30px
canvas
float: left
border: 2px solid black
margin: 20px
.color-group
clear: both
padding-bottom: 10px
margin-bottom: 10px
border-bottom: 1px solid gray
.name-cell
float: left
width: 100px
padding-top: 2px
input
margin-right: 10px
position: relative
top: -3px
.checkbox-cell
float: left
width: 40px
.slider-cell
margin-bottom: 10px
float: left
width: 120px
.selector
width: 100px

View file

@ -24,9 +24,9 @@
width: 300px
height: 80px
//@include transition(color .10s linear) // buggy in chrome, coloring doesn't get the right edge of the word
@include transition(color .10s linear)
&:hover a, &:active a
&:hover button, &:active button
color: #8090AA
canvas

View file

@ -1,20 +1,17 @@
#wizard-settings-modal
color: black
background: white
width: 400px
#wizard-settings-view #color-settings
width: 480px
canvas
border: 1px solid black
float: left
margin: 0px auto 20px
display: block
float: none
background: white
.settings
width: 225px
margin: 10px
display: inline-block
.selector
margin: 10px 5px
button
margin-left: 10px
float: right
.wizard-name-line
text-align: center
margin-bottom: 10px
label
margin-right: 10px

View file

@ -20,3 +20,5 @@
white-space: nowrap
overflow: hidden
#must-log-in button
margin-right: 10px

View file

@ -5,3 +5,8 @@
#competitors-column .well
font-size: 18px
padding: 7px
#your-score
margin-top: 20px
text-align: center
font-size: 20px

View file

@ -132,9 +132,21 @@
height: 100%
width: 2%
.footer:not(:hover)
@include opacity(0.6)
.footer
@media screen and (min-aspect-ratio: 17/10)
display: none
@media screen and (min-aspect-ratio: 17/10)
#level-view .footer
display: none
&:not(:hover)
@include opacity(0.6)
.hour-of-code-explanation
margin-top: 5px
color: white
font-size: 12px
&:not(:hover)
@include opacity(0.75)
a
color: white
text-decoration: underline

View file

@ -77,7 +77,7 @@
.executed
background-color: rgba(245, 255, 6, 0.18)
.problem-marker-info
background-color: rgba(96, 63, 84, 0.25)
background-color: rgba(196, 163, 184, 0.25)
.problem-marker-warning
background-color: rgba(100, 65, 20, 0.25)
.problem-marker-error

View file

@ -9,16 +9,18 @@
border: 1px solid transparent
cursor: pointer
@include user-select(all)
::selection
background: transparent
&:hover
border: 1px solid #BFF
border: 1px solid #000000
&.pinned
background-color: darken(#BFF, 20%)
background-color: darken(#FFFFFF, 25%)
// Pulling these colors from the most relevant textmate-theme classes
&.function
color: #0000A2
color: #0066FF
&.object
color: rgb(6, 150, 14)
&.string

View file

@ -57,7 +57,7 @@ block content
a(href="http://en.gravatar.com/profiles/edit/?noclose#your-images", target="_blank", data-i18n="account_settings.gravatar_add_more_photos") Add more photos to your Gravatar account to access them here.
#wizard-pane.tab-pane
#wizard-settings-tab-view
#wizard-settings-view
#password-pane.tab-pane
p

View file

@ -8,7 +8,7 @@ canvas#tinting-display(width=200, height=200).img-rounded
span(data-i18n='wizard_settings.' + group.dasherized)= group.humanized
div.sliders
div.slider-cell
label(for=group.humanized+"_hue", data-i18n="wizard_settigs.hue") Hue
label(for=group.humanized+"_hue", data-i18n="wizard_settings.hue") Hue
.selector(id=group.humanized+"_hue", name=group.name+'.hue', data-key='hue')
div.slider-cell
label(for=group.humanized+"_saturation", data-i18n="wizard_settings.saturation") Saturation

View file

@ -4,19 +4,17 @@ block modal-header-content
h3(data-i18n="wizard_settings.title") Wizard Settings
block modal-body-content
h4(data-i18n="wizard_settings.customize_avatar") Customize Your Avatar
div.wizard-name-line.form-group
label.control-label(for="name")
| Name
input#wizard-settings-name(name="name", type="text", value="#{me.get('name')||''}")
canvas(width="120px", height="150px")
.settings
.form-vertical.form
.form-group
label.control-label(for="name")
| Name
button.btn.btn-mini.btn-primary#random-name Random
input#wizard-settings-name(name="name", type="text", value="#{me.get('name')||''}")
.form-group
label.control-label(for="wizardColor1") Hat Color
.selector#wizard-settings-color-1
#wizard-settings-view
block modal-body-wait-content
h3 Saving...
.progress.progress-striped.active
.progress-bar
block modal-footer-content
button.btn.btn-primary.btn-large#wizard-settings-done(type="button", data-dismiss="modal", aria-hidden="true") Done
button.btn.btn-primary.btn-large#wizard-settings-done(type="button") Done

View file

@ -6,47 +6,58 @@ block content
div#level-description
!{description}
a(href="http://www.youtube.com/watch?v=IFvfZiJGDsw&list=HL1392928835&feature=mh_lolz").intro-button.btn.btn-primary.btn-lg Watch the Video
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="/play/level/ladder-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial
a(href="/play/level/ladder-tutorial").intro-button.btn.btn-primary.btn-lg Play the Tutorial
hr
div#columns.row
for team in teams
div.column.col-md-6
a(href="/play/ladder/#{levelID}/team/#{team.id}", style="background-color: #{team.primaryColor}").play-button.btn.btn-danger.btn-block.btn-lg
span Play As
span= team.name
table.table.table-bordered.table-condensed.table-hover
//(style="background-color: #{team.bgColor}")
tr
th(colspan=3, style="color: #{team.primaryColor}")
span= team.name
span Leaderboard
tr
th Score
th.name-col-cell Name
th
if me.get('anonymous')
div#must-log-in
p
strong Please log in first before playing a ladder game.
button.btn.btn-primary(data-toggle="coco-modal", data-target="modal/login", data-i18n="login.log_in") Log In
button.btn.btn-primary(data-toggle="coco-modal", data-target="modal/signup", data-i18n="login.sign_up") Create Account
for session in team.leaderboard.topPlayers.models
- var myRow = session.get('creator') == me.id
tr(class=myRow ? "success" : "")
td.score-cell= session.get('totalScore').toFixed(2)
td.name-col-cell= session.get('creatorName') || "Anonymous"
td
if(!myRow)
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}!
else
a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches.
unless me.attributes.anonymous
hr
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px;") Simulate Games!
p(id="simulation-status-text", style="display:inline; margin-left:10px;")
if simulationStatus
| #{simulationStatus}
else
| By simulating games you can get your game ranked faster!
if me.isAdmin()
button.btn.btn-danger.btn-lg.highlight#simulate-all-button(style="margin-bottom:10px; float: right;") RESET AND SIMULATE GAMES
else
div#columns.row
for team in teams
div.column.col-md-6
a(href="/play/ladder/#{levelID}/team/#{team.id}", style="background-color: #{team.primaryColor}").play-button.btn.btn-danger.btn-block.btn-lg
span Play As
span= team.name
table.table.table-bordered.table-condensed.table-hover
//(style="background-color: #{team.bgColor}")
tr
th(colspan=3, style="color: #{team.primaryColor}")
span= team.name
span Leaderboard
tr
th Score
th.name-col-cell Name
th
for session in team.leaderboard.topPlayers.models
- var myRow = session.get('creator') == me.id
tr(class=myRow ? "success" : "")
td.score-cell= session.get('totalScore').toFixed(2)
td.name-col-cell= session.get('creatorName') || "Anonymous"
td
if(!myRow)
a(href="/play/level/#{level.get('slug') || level.id}/?team=#{team.otherTeam}&opponent=#{session.id}") Battle as #{team.otherTeam}!
else
a(href="/play/ladder/#{levelID}/team/#{team.id}") View your #{team.id} matches.
unless me.attributes.anonymous
hr
button.btn.btn-warning.btn-lg.highlight#simulate-button(style="margin-bottom:10px;") Simulate Games!
p(id="simulation-status-text", style="display:inline; margin-left:10px;")
if simulationStatus
| #{simulationStatus}
else
| By simulating games you can get your game ranked faster!
if me.isAdmin()
button.btn.btn-danger.btn-lg.highlight#simulate-all-button(style="margin-bottom:10px; float: right;") RESET AND SIMULATE GAMES

View file

@ -16,6 +16,13 @@ block content
p
| After your first submission, your code will also continuously run against other players as they rank themselves.
if matches.length
p#your-score
span Your Current Score:
span
strong= score
div#columns.row
div#matches-column.col-md-6
h3.pull-left Ranked Games

View file

@ -27,3 +27,11 @@
.content
p(class='footer-link-text')
a(title='Send CodeCombat a message', tabindex=-1, data-toggle="coco-modal", data-target="modal/contact", data-i18n="nav.contact") Contact
if explainHourOfCode
// Does not show up unless lang is en-US.
div.hour-of-code-explanation
| The 'Hour of Code' is a nationwide initiative by
a(href="http://csedweek.org") Computer Science Education Week
| and
a(href="http://code.org") Code.org
| to introduce millions of students to one hour of computer science and computer programming.

View file

@ -1,6 +1,7 @@
.thang-avatar-wrapper(class="team-" + (thang.team || "neutral"))
//canvas(width=100, height=100, title=thang.id + " - " + thang.team)
img.img-responsive(src=avatarURL, title=thang.id + " - " + thang.team)
- var title = thang.id + " - " + thang.team + (thang.type ? ' - type: "' + thang.type + '"' : '')
img.img-responsive(src=avatarURL, title=title)
.badge.problems
.badge.shared-thangs
if includeName

View file

@ -4,7 +4,7 @@ template = require 'templates/account/settings'
forms = require('lib/forms')
User = require('models/User')
WizardSettingsTabView = require './wizard_settings_tab_view'
WizardSettingsView = require './wizard_settings_view'
module.exports = class SettingsView extends View
id: 'account-settings-view'
@ -26,11 +26,12 @@ module.exports = class SettingsView extends View
refreshPicturePane: =>
h = $(@template(@getRenderData()))
new_pane = $('#picture-pane', h)
old_pane = $('#picture-pane')
active = old_pane.hasClass('active')
old_pane.replaceWith(new_pane)
new_pane.addClass('active') if active
newPane = $('#picture-pane', h)
oldPane = $('#picture-pane')
active = oldPane.hasClass('active')
oldPane.replaceWith(newPane)
newPane.i18n()
newPane.addClass('active') if active
afterRender: ->
super()
@ -46,9 +47,9 @@ module.exports = class SettingsView extends View
@chooseTab(location.hash.replace('#',''))
@updateWizardColor()
wizardSettingsTabView = new WizardSettingsTabView()
wizardSettingsTabView.on 'change', @save, @
@insertSubView wizardSettingsTabView
WizardSettingsView = new WizardSettingsView()
WizardSettingsView.on 'change', @save, @
@insertSubView WizardSettingsView
chooseTab: (category) ->
id = "##{category}-pane"

View file

@ -1,12 +1,13 @@
RootView = require 'views/kinds/RootView'
template = require 'templates/account/wizard_settings_tab'
CocoView = require 'views/kinds/CocoView'
template = require 'templates/account/wizard_settings'
{me} = require('lib/auth')
ThangType = require 'models/ThangType'
SpriteBuilder = require 'lib/sprites/SpriteBuilder'
module.exports = class WizardSettingsTabView extends RootView
id: 'wizard-settings-tab-view'
module.exports = class WizardSettingsView extends CocoView
id: 'wizard-settings-view'
template: template
startsLoading: true
events:
'change .color-group-checkbox': (e) ->
@ -25,6 +26,7 @@ module.exports = class WizardSettingsTabView extends RootView
@wizardThangType.once 'sync', @initCanvas, @
initCanvas: ->
@startsLoading = false
@render()
@spriteBuilder = new SpriteBuilder(@wizardThangType)
@initStage()
@ -44,6 +46,7 @@ module.exports = class WizardSettingsTabView extends RootView
c
afterRender: ->
return if @startsLoading
wizardSettings = me.get('wizard') or {}
wizardSettings.colorConfig ?= {}
@ -82,8 +85,6 @@ module.exports = class WizardSettingsTabView extends RootView
initStage: ->
@stage = new createjs.Stage(@$el.find('canvas')[0])
@updateMovieClip()
createjs.Ticker.setFPS 20
createjs.Ticker.addEventListener("tick", @stage)
updateMovieClip: ->
return unless @wizardThangType.loaded
@ -104,6 +105,3 @@ module.exports = class WizardSettingsTabView extends RootView
@movieClip.regY = reg.y
@stage.addChild @movieClip
@stage.update()
destroy: ->
@stage?.removeAllEventListeners()

View file

@ -48,15 +48,10 @@ module.exports = class ThangsTabView extends View
'click #extant-thangs-filter button': 'onFilterExtantThangs'
shortcuts:
'esc': -> @selectAddThang()
onFilterExtantThangs: (e) ->
button = $(e.target).closest('button')
button.button('toggle')
val = button.val()
@thangsTreema.$el.removeClass(@lastHideClass) if @lastHideClass
@thangsTreema.$el.addClass(@lastHideClass = "hide-except-#{val}") if val
'esc': 'selectAddThang'
'delete, del, backspace': 'deleteSelectedExtantThang'
'left': -> @moveAddThangSelection -1
'right': -> @moveAddThangSelection 1
constructor: (options) ->
super options
@ -102,12 +97,12 @@ module.exports = class ThangsTabView extends View
$('#thangs-list').bind 'mousewheel', @preventBodyScrollingInThangList
@$el.find('#extant-thangs-filter button:first').button('toggle')
# TODO: move these into the shortcuts list
key 'left', _.bind @moveAddThangSelection, @, -1
key 'right', _.bind @moveAddThangSelection, @, 1
key 'delete, del, backspace', @deleteSelectedExtantThang
key 'f', => Backbone.Mediator.publish('level-set-debug', debug: not @surface.debug)
key 'g', => Backbone.Mediator.publish('level-set-grid', grid: not @surface.gridShowing())
onFilterExtantThangs: (e) ->
button = $(e.target).closest('button')
button.button('toggle')
val = button.val()
@thangsTreema.$el.removeClass(@lastHideClass) if @lastHideClass
@thangsTreema.$el.addClass(@lastHideClass = "hide-except-#{val}") if val
preventBodyScrollingInThangList: (e) ->
@scrollTop += (if e.deltaY < 0 then 1 else -1) * 30

View file

@ -129,7 +129,7 @@ module.exports = class CocoView extends Backbone.View
# Loading RootViews
showLoading: ($el=@$el) ->
$el.find('>').hide()
$el.find('>').addClass('hidden')
$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>'))
@ -138,7 +138,7 @@ module.exports = class CocoView extends Backbone.View
hideLoading: ->
return unless @_lastLoading?
@_lastLoading.find('.loading-screen').remove()
@_lastLoading.find('>').show()
@_lastLoading.find('>').removeClass('hidden')
@_lastLoading = null
# Loading ModalViews

View file

@ -22,8 +22,8 @@ module.exports = class RootView extends CocoView
logoutUser($('#login-email').val())
showWizardSettingsModal: ->
WizardSettingsView = require('views/modal/wizard_settings_modal')
subview = new WizardSettingsView {}
WizardSettingsModal = require('views/modal/wizard_settings_modal')
subview = new WizardSettingsModal {}
@openModalView subview
showLoading: ($el) ->

View file

@ -2,79 +2,44 @@ View = require 'views/kinds/ModalView'
template = require 'templates/modal/wizard_settings'
WizardSprite = require 'lib/surface/WizardSprite'
ThangType = require 'models/ThangType'
{me} = require 'lib/auth'
forms = require('lib/forms')
module.exports = class WizardSettingsView extends View
module.exports = class WizardSettingsModal extends View
id: "wizard-settings-modal"
template: template
closesOnClickOutside: false
events:
'change #wizard-settings-name': 'onNameChange'
'click #random-name': 'onRandomNameClick'
'click #wizard-settings-done': 'saveSettings'
render: ->
me.set('name', @randomName()) if not me.get('name')
super()
onRandomNameClick: =>
$('#wizard-settings-name').val(@randomName())
@saveSettings()
randomName: ->
return NameGenerator.getName(7, 9)
'click #wizard-settings-done': 'onWizardSettingsDone'
afterRender: ->
super()
@colorSlider = $( "#wizard-settings-color-1", @$el).slider({ animate: "fast" })
@colorSlider.slider('value', me.get('wizardColor1')*100)
@colorSlider.on('slide',@onSliderChange)
@colorSlider.on('slidechange',@onSliderChange)
@stage = new createjs.Stage($('canvas', @$el)[0])
@saveChanges = _.debounce(@saveChanges, 1000)
WizardSettingsView = require 'views/account/wizard_settings_view'
view = new WizardSettingsView()
@insertSubView view
wizOriginal = "52a00d55cf1818f2be00000b"
url = "/db/thang_type/#{wizOriginal}/version"
@wizardType = new ThangType()
@wizardType.url = -> url
@wizardType.fetch()
@wizardType.once 'sync', @initCanvas
initCanvas: =>
spriteOptions = thangID: "Config Wizard", resolutionFactor: 3
@wizardSprite = new WizardSprite @wizardType, spriteOptions
@wizardSprite.setColorHue(me.get('wizardColor1'))
@wizardDisplayObject = @wizardSprite.displayObject
@wizardDisplayObject.x = 10
@wizardDisplayObject.y = 15
@wizardDisplayObject.scaleX = @wizardDisplayObject.scaleY = 3.0
@stage.addChild(@wizardDisplayObject)
@updateSpriteColor()
@stage.update()
onSliderChange: =>
@updateSpriteColor()
@saveSettings()
getColorHue: ->
@colorSlider.slider('value') / 100
updateSpriteColor: ->
colorHue = @getColorHue()
@wizardSprite.setColorHue(colorHue)
@stage.update()
onNameChange: =>
@saveSettings()
saveSettings: ->
onNameChange: ->
me.set('name', $('#wizard-settings-name').val())
me.set('wizardColor1', @getColorHue())
@saveChanges()
saveChanges: ->
onWizardSettingsDone: ->
forms.clearFormAlerts(@$el)
res = me.validate()
if res?
forms.applyErrorsToForm(@$el, res)
return
res = me.save()
return unless res
save = $('#save-button', @$el).text($.i18n.t('common.saving', defaultValue: 'Saving...'))
.addClass('btn-info').show().removeClass('btn-danger')
res.error =>
errors = JSON.parse(res.responseText)
forms.applyErrorsToForm(@$el, errors)
@disableModalInProgress(@$el)
res.success (model, response, options) =>
@hide()
@enableModalInProgress(@$el)
me.save()
destroy: ->
@wizardSprite?.destroy()
super()

View file

@ -90,6 +90,7 @@ module.exports = class LadderTeamView extends RootView
ctx.matches = (convertMatch(match) for match in @session.get('matches') or [])
ctx.matches.reverse()
ctx.score = (@session.get('totalScore') or 10).toFixed(2)
ctx
afterRender: ->

View file

@ -36,8 +36,8 @@ module.exports = class PlaybackView extends View
shortcuts:
'⌘+p, p, ctrl+p': 'onTogglePlay'
'[': 'onScrubBack'
']': 'onScrubForward'
'⌘+[, ctrl+[': 'onScrubBack'
'⌘+], ctrl+]': 'onScrubForward'
constructor: ->
super(arguments...)
@ -169,7 +169,7 @@ module.exports = class PlaybackView extends View
if @clickingSlider
@clickingSlider = false
@wasPlaying = false
@onSetPlaying {playing: false}
Backbone.Mediator.publish 'level-set-playing', {playing: false}
@$el.find('.scrubber-handle').effect('bounce', {times: 2})
)

View file

@ -17,7 +17,7 @@ module.exports = class CastButtonView extends View
@spells = options.spells
@levelID = options.levelID
isMac = navigator.platform.toUpperCase().indexOf('MAC') isnt -1
@castShortcut = ""
@castShortcut = ""
@castShortcutVerbose = "Shift+Enter"
getRenderData: (context={}) ->
@ -35,7 +35,7 @@ module.exports = class CastButtonView extends View
# TODO: use a User setting instead of localStorage
delay = localStorage.getItem 'autocastDelay'
delay ?= 5000
if @levelID is 'project-dota'
if @levelID in ['project-dota', 'brawlwood', 'ladder-tutorial']
delay = 90019001
@setAutocastDelay delay

View file

@ -21,6 +21,10 @@ module.exports = class DebugView extends View
@ace = options.ace
@thang = options.thang
@variableStates = {}
@globals = {Math: Math, _: _} # ... add more as documented
for className, klass of serializedClasses
@globals[className] = klass
@onMouseMove = _.throttle @onMouseMove, 25
afterRender: ->
super()
@ -30,10 +34,11 @@ module.exports = class DebugView extends View
@update()
onMouseMove: (e) =>
return if @destroyed
pos = e.getDocumentPosition()
endOfDoc = pos.row is @ace.getSession().getDocument().getLength() - 1
it = new TokenIterator e.editor.session, pos.row, pos.column
isIdentifier = (t) -> t and (t.type is 'identifier' or t.value is 'this')
isIdentifier = (t) => t and (t.type is 'identifier' or t.value is 'this' or @globals[t.value])
while it.getCurrentTokenRow() is pos.row and not isIdentifier(token = it.getCurrentToken())
it.stepBackward()
break unless token
@ -52,7 +57,7 @@ module.exports = class DebugView extends View
token = prev
start = it.getCurrentTokenColumn()
chain.unshift token.value
if token and (token.value of @variableStates or token.value is "this")
if token and (token.value of @variableStates or token.value is "this" or @globals[token.value])
@variableChain = chain
offsetX = e.domEvent.offsetX ? e.clientX - $(e.domEvent.target).offset().left
offsetY = e.domEvent.offsetY ? e.clientY - $(e.domEvent.target).offset().top
@ -76,6 +81,10 @@ module.exports = class DebugView extends View
@$el.show().css(@pos)
else
@$el.hide()
if @variableChain?.length is 2
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: @variableChain[1], owner: @variableChain[0]
else
Backbone.Mediator.publish 'tome:spell-debug-property-hovered', property: null
@updateMarker()
updateMarker: ->
@ -92,7 +101,7 @@ module.exports = class DebugView extends View
return "<this #{value.id}>" if value is @thang and depth
if depth is 2
if value.constructor?.className is "Thang"
value = "<#{value.spriteName} - #{value.id}, #{if value.pos then value.pos.toString() else 'non-physical'}>"
value = "<#{value.type or value.spriteName} - #{value.id}, #{if value.pos then value.pos.toString() else 'non-physical'}>"
else
value = value.toString()
return value
@ -124,8 +133,11 @@ module.exports = class DebugView extends View
for prop, i in chain
if prop is "this"
value = @thang
else if i is 0
value = @variableStates[prop]
if typeof value is "undefined" then value = @globals[prop]
else
value = (if i is 0 then @variableStates else value)[prop]
value = value[prop]
keys.push prop
break unless value
if theClass = serializedClasses[value.CN]

View file

@ -74,7 +74,7 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
formatPopover: (doc) ->
content = popoverTemplate doc: doc, marked: marked, argumentExamples: (arg.example or arg.default or arg.name for arg in doc.args ? [])
owner = @thang
content = content.replace /#{spriteName}/g, @thang.spriteName # No quotes like we'd get with @formatValue
content = content.replace /#{spriteName}/g, @thang.type ? @thang.spriteName # Prefer type, and excluded the quotes we'd get with @formatValue
content.replace /\#\{(.*?)\}/g, (s, properties) => @formatValue downTheChain(owner, properties.split('.'))
formatValue: (v) ->
@ -101,12 +101,15 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView
onClick: (e) -> # Don't call super
onDropdownClick: (e) ->
return unless @controlsEnabled
Backbone.Mediator.publish 'tome:toggle-spell-list'
onCodeReload: ->
return unless @controlsEnabled
Backbone.Mediator.publish "tome:reload-code", spell: @spell
onBeautifyClick: ->
return unless @controlsEnabled
Backbone.Mediator.publish "spell-beautify", spell: @spell
updateReloadButton: ->

View file

@ -66,6 +66,7 @@ module.exports = class SpellPaletteEntryView extends View
'surface:frame-changed': "onFrameChanged"
'tome:palette-hovered': "onPaletteHovered"
'tome:palette-pin-toggled': "onPalettePinToggled"
'tome:spell-debug-property-hovered': 'onSpellDebugPropertyHovered'
events:
'mouseenter': 'onMouseEnter'
@ -83,7 +84,11 @@ module.exports = class SpellPaletteEntryView extends View
@doc.shortName = @doc.shorterName = @doc.title = @doc.name
else
@doc.owner ?= 'this'
suffix = if @doc.type is 'function' then '()' else ''
suffix = ''
if @doc.type is 'function'
argNames = (arg.name for arg in @doc.args ? []).join(', ')
argNames = '...' if argNames.length > 6
suffix = "(#{argNames})"
@doc.shortName = "#{@doc.owner}.#{@doc.name}#{suffix};"
if @doc.owner is 'this' or options.tabbify
@doc.shorterName = "#{@doc.name}#{suffix}"
@ -184,6 +189,16 @@ module.exports = class SpellPaletteEntryView extends View
return if e.entry is @
@otherPopoverPinned = e.pinned
onSpellDebugPropertyHovered: (e) ->
matched = e.property is @doc.name and e.owner is @doc.owner
if matched and not @debugHovered
@debugHovered = true
@togglePinned() unless @popoverPinned
else if @debugHovered and not matched
@debugHovered = false
@togglePinned() if @popoverPinned
null
destroy: ->
$('.popover.pinned').remove() if @popoverPinned # @$el.popover('destroy') doesn't work
@togglePinned() if @popoverPinned

View file

@ -46,24 +46,42 @@ module.exports = class SpellPaletteView extends View
for doc in (lc.get('propertyDocumentation') ? [])
allDocs[doc.name] ?= []
allDocs[doc.name].push doc
if doc.type is 'snippet' then doc.owner = 'snippets'
#allDocs[doc.name] = doc for doc in (lc.get('propertyDocumentation') ? []) for lc in lcs
props = _.sortBy @thang.programmableProperties ? []
snippets = _.sortBy @thang.programmableSnippets ? []
shortenize = props.length + snippets.length > 6
tabbify = props.length + snippets.length >= 10
propStorage =
'this': 'programmableProperties'
more: 'moreProgrammableProperties'
Math: 'programmableMathProperties'
Vector: 'programmableVectorProperties'
snippets: 'programmableSnippets'
count = 0
propGroups = {}
for owner, storage of propStorage
added = propGroups[owner] = _.sortBy(@thang[storage] ? []).slice()
count += added.length
shortenize = count > 6
tabbify = count >= 10
@entries = []
for type, props of {props: props.slice(), snippets: snippets.slice()}
for owner, props of propGroups
for prop in props
doc = allDocs[prop]?.shift() ? prop # Add one doc per instance of the prop name (this is super gimp)
@entries.push @addEntry(doc, shortenize, tabbify, type is 'snippets')
doc = _.find (allDocs[prop] ? []), (doc) ->
return true if doc.owner is owner
return (owner is 'this' or owner is 'more') and (not doc.owner? or doc.owner is 'this')
console.log 'could not find doc for', prop, 'from', allDocs[prop], 'for', owner, 'of', propGroups unless doc
doc ?= prop
@entries.push @addEntry(doc, shortenize, tabbify, owner is 'snippets')
groupForEntry = (entry) ->
return 'more' if entry.doc.owner is 'this' and entry.doc.name in propGroups.more
entry.doc.owner
@entries = _.sortBy @entries, (entry) ->
order = ['this', 'Math', 'Vector', 'snippets']
index = order.indexOf entry.doc.owner
order = ['this', 'more', 'Math', 'Vector', 'snippets']
index = order.indexOf groupForEntry entry
index = String.fromCharCode if index is -1 then order.length else index
index += entry.doc.name
if tabbify and _.find @entries, ((entry) -> entry.doc.owner isnt 'this')
@entryGroups = _.groupBy @entries, (entry) -> entry.doc.owner
@entryGroups = _.groupBy @entries, groupForEntry
else
defaultGroup = $.i18n.t("play_level.tome_available_spells", defaultValue: "Available Spells")
@entryGroups = {}

View file

@ -326,7 +326,10 @@ module.exports = class SpellView extends View
isCast = not _.isEmpty(aether.metrics) or _.some aether.problems.errors, {type: 'runtime'}
@problems = []
annotations = []
seenProblemKeys = {}
for aetherProblem, problemIndex in aether.getAllProblems()
continue if aetherProblem.userInfo.key of seenProblemKeys
seenProblemKeys[aetherProblem.userInfo.key] = true
@problems.push problem = new Problem aether, aetherProblem, @ace, isCast and problemIndex is 0, isCast
annotations.push problem.annotation if problem.annotation
@aceSession.setAnnotations annotations
@ -452,11 +455,11 @@ module.exports = class SpellView extends View
markerRange.end.detach()
@aceSession.removeMarker markerRange.id
@markerRanges = []
@debugView.setVariableStates {}
@aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()]
$(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing')
if not executed.length or (@spell.name is "plan" and @spellThang.castAether.metrics.statementsExecuted < 20)
@toolbarView?.toggleFlow false
@debugView.setVariableStates {}
return
lastExecuted = _.last executed
@toolbarView?.toggleFlow true
@ -464,6 +467,7 @@ module.exports = class SpellView extends View
@toolbarView?.setCallState states[currentCallIndex], statementIndex, currentCallIndex, @spellThang.castAether.metrics
marked = {}
lastExecuted = lastExecuted[0 .. @toolbarView.statementIndex] if @toolbarView?.statementIndex?
gotVariableStates = false
for state, i in lastExecuted
[start, end] = state.range
clazz = if i is lastExecuted.length - 1 then 'executing' else 'executed'
@ -473,6 +477,7 @@ module.exports = class SpellView extends View
markerType = "fullLine"
else
@debugView.setVariableStates state.variables
gotVariableStates = true
markerType = "text"
markerRange = new Range start.row, start.col, end.row, end.col
markerRange.start = @aceDoc.createAnchor markerRange.start
@ -480,6 +485,7 @@ module.exports = class SpellView extends View
markerRange.id = @aceSession.addMarker markerRange, clazz, markerType
@markerRanges.push markerRange
@aceSession.addGutterDecoration start.row, clazz if clazz is 'executing'
@debugView.setVariableStates {} unless gotVariableStates
null
highlightComments: ->

View file

@ -21,6 +21,7 @@ module.exports = class ThangListEntryView extends View
'surface:frame-changed': "onFrameChanged"
'level-set-letterbox': 'onSetLetterbox'
'tome:thang-list-entry-popover-shown': 'onThangListEntryPopoverShown'
'surface:coordinates-shown': 'onSurfaceCoordinatesShown'
events:
'click': 'onClick'
@ -86,38 +87,47 @@ module.exports = class ThangListEntryView extends View
onMouseEnter: (e) ->
return unless @controlsEnabled and @spells.length
@showSpells()
@clearTimeouts()
@showSpellsTimeout = _.delay @showSpells, 100
onMouseLeave: (e) ->
return unless @controlsEnabled and @spells.length
clearTimeout @hideSpellsTimeout if @hideSpellsTimeout
@clearTimeouts()
@hideSpellsTimeout = _.delay @hideSpells, 100
clearTimeouts: ->
clearTimeout @showSpellsTimeout if @showSpellsTimeout
clearTimeout @hideSpellsTimeout if @hideSpellsTimeout
@showSpellsTimeout = @hideSpellsTimeout = null
onThangListEntryPopoverShown: (e) ->
# I couldn't figure out how to get the mouseenter / mouseleave to always work, so this is a fallback
# to hide our popover is another Thang's popover gets shown.
return if e.entry is @
@hideSpells()
onSurfaceCoordinatesShown: (e) ->
# Definitely aren't hovering over this.
@hideSpells()
showSpells: =>
@clearTimeouts()
@sortSpells()
@$el.data('bs.popover').options.content = @getSpellListHTML()
@$el.popover('setContent').popover('show')
@$el.parent().parent().parent().i18n()
clearTimeout @hideSpellsTimeout if @hideSpellsTimeout
@hideSpellsTimeout = null
@popover = @$el.parent().parent().parent().find('.popover')
@popover.off 'mouseenter mouseleave'
@popover.mouseenter (e) => @onMouseEnter()
@popover.mouseleave (e) => @onMouseLeave()
@popover.mouseenter (e) => @showSpells() if @controlsEnabled
@popover.mouseleave (e) => @hideSpells()
thangID = @thang.id
@popover.find('code').click (e) ->
Backbone.Mediator.publish "level-select-sprite", thangID: thangID, spellName: $(@).data 'spell-name'
Backbone.Mediator.publish 'tome:thang-list-entry-popover-shown', entry: @
hideSpells: =>
@clearTimeouts()
@$el.popover('hide')
@hideSpellsTimeout = null
getSpellListHTML: ->
spellsPopoverTemplate {spells: @spells}
@ -129,13 +139,16 @@ module.exports = class ThangListEntryView extends View
onSetLetterbox: (e) ->
if e.on then @reasonsToBeDisabled.letterbox = true else delete @reasonsToBeDisabled.letterbox
@updateControls()
onDisableControls: (e) ->
return if e.controls and not ('surface' in e.controls) # disable selection?
@reasonsToBeDisabled.controls = true
@updateControls()
onEnableControls: (e) ->
delete @reasonsToBeDisabled.controls
@updateControls()
updateControls: ->
enabled = _.keys(@reasonsToBeDisabled).length is 0
return if enabled is @controlsEnabled

View file

@ -151,9 +151,15 @@ module.exports = class TomeView extends View
@cast()
cast: ->
for spellKey, spell of @spells
for thangID, spellThang of spell.thangs
spellThang.aether.options.includeFlow = spellThang.aether.originalOptions.includeFlow = spellThang is @spellView?.spellThang
if @options.levelID is 'project-dota'
# For performance reasons, only includeFlow on the currently Thang.
for spellKey, spell of @spells
for thangID, spellThang of spell.thangs
hadFlow = Boolean spellThang.aether.options.includeFlow
willHaveFlow = spellThang is @spellView?.spellThang
spellThang.aether.options.includeFlow = spellThang.aether.originalOptions.includeFlow = willHaveFlow
spellThang.aether.transpile spell.source unless hadFlow is willHaveFlow
#console.log "set includeFlow to", spellThang.aether.options.includeFlow, "for", thangID, "of", spellKey
Backbone.Mediator.publish 'tome:cast-spells', spells: @spells
onToggleSpellList: (e) ->

View file

@ -116,6 +116,10 @@ module.exports = class PlayLevelView extends View
getRenderData: ->
c = super()
c.world = @world
if me.get('hourOfCode') and me.lang() is 'en-US'
# Show the Hour of Code footer explanation until it's been more than a day
elapsed = (new Date() - new Date(me.get('dateCreated')))
c.explainHourOfCode = elapsed < 86400 * 1000
c
afterRender: ->
@ -194,7 +198,7 @@ module.exports = class PlayLevelView extends View
afterInsert: ->
super()
# @showWizardSettingsModal() if not me.get('name')
@showWizardSettingsModal() if not me.get('name')
# callbacks

View file

@ -8,5 +8,5 @@ current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
coco_path = os.getenv("COCO_DIR",os.path.join(current_directory,os.pardir))
nodemon_path = coco_path + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "nodemon"
call(nodemon_path + " . --ext \".coffee|.js\" --watch server --watch app.js --watch server_config.js",shell=True,cwd=coco_path)
call(nodemon_path + " . --ext \".coffee|.js\" --watch server --watch app.js --watch server_config.js --watch server_setup.coffee",shell=True,cwd=coco_path)

View file

@ -24,6 +24,25 @@ connectToScoringQueue = ->
scoringTaskQueue = data
log.info "Connected to scoring task queue!"
module.exports.addPairwiseTaskToQueue = (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
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"
LevelSession.find(_id:taskPair[1]).exec (err, secondSession) =>
if err? then return errors.serverError res, "There was an error fetching the second session"
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"
sendResponseObject req, res, {"message":"All task pairs were succesfully sent to the queue"}
module.exports.createNewTask = (req, res) ->
requestSessionID = req.body.session
validatePermissions req, requestSessionID, (error, permissionsAreValid) ->

View file

@ -5,6 +5,7 @@ UserHandler = require('../users/user_handler')
config = require '../../server_config'
errors = require '../commons/errors'
mail = require '../commons/mail'
languages = require '../routes/languages'
module.exports.setup = (app) ->
authentication.serializeUser((user, done) -> done(null, user._id))
@ -43,10 +44,36 @@ module.exports.setup = (app) ->
)
app.get('/auth/whoami', (req, res) ->
res.setHeader('Content-Type', 'text/json');
if req.user
sendSelf(req, res)
else
user = new User({anonymous:true})
user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth
user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
makeNext = (req, res) -> -> sendSelf(req, res)
next = makeNext(req, res)
loginUser(req, res, user, false, next)
)
sendSelf = (req, res) ->
res.setHeader('Content-Type', 'text/json')
res.send(UserHandler.formatEntity(req, req.user))
res.end()
)
loginUser = (req, res, user, send=true, next=null) ->
user.save((err) ->
if err
return @sendDatabaseError(res, err)
req.logIn(user, (err) ->
if err
return @sendDatabaseError(res, err)
if send
return @sendSuccess(res, user)
next() if next
)
)
app.post('/auth/logout', (req, res) ->
req.logout()

View file

@ -6,6 +6,10 @@ scoringQueue = require '../queues/scoring'
module.exports.setup = (app) ->
scoringQueue.setup()
#app.post '/queue/scoring/pairwise', (req, res) ->
# handler = loadQueueHandler 'scoring'
# handler.addPairwiseTaskToQueue req, res
app.all '/queue/*', (req, res) ->
setResponseHeaderToJSONContentType res
@ -24,6 +28,7 @@ module.exports.setup = (app) ->
log.error error
sendQueueError req, res, error
setResponseHeaderToJSONContentType = (res) -> res.setHeader('Content-Type', 'application/json')
getQueueNameFromPath = (path) ->

View file

@ -3,7 +3,6 @@ crypto = require 'crypto'
request = require 'request'
User = require './User'
Handler = require '../commons/Handler'
languages = require '../routes/languages'
mongoose = require 'mongoose'
config = require '../../server_config'
errors = require '../commons/errors'
@ -172,28 +171,3 @@ UserHandler = class UserHandler extends Handler
res.end()
module.exports = new UserHandler()
module.exports.setupMiddleware = (app) ->
app.use (req, res, next) ->
if req.user
next()
else
user = new User({anonymous:true})
user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth
user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
loginUser(req, res, user, false, next)
loginUser = (req, res, user, send=true, next=null) ->
user.save((err) ->
if err
return @sendDatabaseError(res, err)
req.logIn(user, (err) ->
if err
return @sendDatabaseError(res, err)
if send
return @sendSuccess(res, user)
next() if next
)
)

View file

@ -40,9 +40,6 @@ setupOneSecondDelayMiddlware = (app) ->
if(config.slow_down)
app.use((req, res, next) -> setTimeout((-> next()), 1000))
setupUserMiddleware = (app) ->
user.setupMiddleware(app)
setupMiddlewareToSendOldBrowserWarningWhenPlayersViewLevelDirectly = (app) ->
isOldBrowser = (req) ->
# https://github.com/biggora/express-useragent/blob/master/lib/express-useragent.js
@ -66,7 +63,6 @@ exports.setupMiddleware = (app) ->
setupExpressMiddleware app
setupPassportMiddleware app
setupOneSecondDelayMiddlware app
setupUserMiddleware app
###Routing function implementations###