From 8af62d53cda0d5b1e75fc2f994d078327eed2da9 Mon Sep 17 00:00:00 2001
From: Tay Yang Shun <tay.yang.shun@gmail.com>
Date: Sat, 1 Mar 2014 01:55:30 +0800
Subject: [PATCH 1/6] Redirect to level editor only after modal hidden event is
 triggered. Fixes #438.

---
 app/views/kinds/SearchView.coffee | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/app/views/kinds/SearchView.coffee b/app/views/kinds/SearchView.coffee
index 304a9e958..5e150c43a 100644
--- a/app/views/kinds/SearchView.coffee
+++ b/app/views/kinds/SearchView.coffee
@@ -24,6 +24,7 @@ module.exports = class ThangTypeHomeView extends View
     'click button.new-model-submit': 'makeNewModel'
     'submit form': 'makeNewModel'
     'shown.bs.modal #new-model-modal': 'focusOnName'
+    'hidden.bs.modal #new-model-modal': 'onModalHidden'
 
   getRenderData: ->
     c = super()
@@ -85,16 +86,21 @@ module.exports = class ThangTypeHomeView extends View
     res = model.save()
     return unless res
 
-    modal = @$el.find('.modal')
+    modal = @$el.find('#new-model-modal')
     forms.clearFormAlerts(modal)
     @showLoading(modal.find('.modal-body'))
     res.error =>
       @hideLoading()
       forms.applyErrorsToForm(modal, JSON.parse(res.responseText))
+    that = @
     res.success ->
-      modal.modal('hide')
-      base = document.location.pathname[1..] + '/'
-      app.router.navigate(base + (model.get('slug') or model.id), {trigger:true})
+      that.model = model
+      modal.modal('hide')      
+
+  onModalHidden: ->
+    # Can only redirect after the modal hidden event has triggered
+    base = document.location.pathname[1..] + '/'
+    app.router.navigate(base + (@model.get('slug') or @model.id), {trigger:true})
 
   focusOnName: ->
     @$el.find('#name').focus()

From 3cee341887eda2927689bc6d57bebb2dccab49f3 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Fri, 28 Feb 2014 11:27:32 -0800
Subject: [PATCH 2/6] Big performance improvements for scrubbing between
 frames; look out for bugs.

---
 app/lib/surface/Surface.coffee | 56 ++++++++++++++++++++--------------
 1 file changed, 33 insertions(+), 23 deletions(-)

diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee
index 5d44faa62..7d9cf0c08 100644
--- a/app/lib/surface/Surface.coffee
+++ b/app/lib/surface/Surface.coffee
@@ -100,7 +100,7 @@ module.exports = Surface = class Surface extends CocoClass
     @stage.removeAllEventListeners()
     @stage.enableDOMEvents false
     @stage.enableMouseOver 0
-    @playScrubbedSounds = null
+    @onFramesScrubbed = null
     @onMouseMove = null
     @onMouseDown = null
     @tick = null
@@ -202,32 +202,33 @@ module.exports = Surface = class Surface extends CocoClass
         .get(@)
         .to({currentFrame:@scrubbingTo}, scrubDuration, createjs.Ease.sineInOut)
         .call(onTweenEnd)
-      t.addEventListener('change', @playScrubbedSounds)
+      t.addEventListener('change', @onFramesScrubbed)
     else
       @currentFrame = @scrubbingTo
-      @playScrubbedSounds()
+      @onFramesScrubbed()  # For performance, don't play these for instant transitions.
       onTweenEnd()
 
     @updateState true
     @onFrameChanged()
 
-  playScrubbedSounds: (e) =>
-    # gotta play all the sounds, even when scrubbing!
-    rising = @currentFrame > @lastFrame
-    actualCurrentFrame = @currentFrame
-    tempFrame = if rising then Math.ceil(@lastFrame) else Math.floor(@lastFrame)
-    while true  # temporary fix to stop cacophony
-      break if rising and tempFrame > actualCurrentFrame
-      break if (not rising) and tempFrame < actualCurrentFrame
-      @currentFrame = tempFrame
-      frame = @world.getFrame(@getCurrentFrame())
-      frame.restoreState()
-      for thangID, sprite of @spriteBoss.sprites
-        sprite.playSounds false, Math.max(0.05, Math.min(1, 1 / @scrubbingPlaybackSpeed))
-      tempFrame += if rising then 1 else -1
-    @currentFrame = actualCurrentFrame
+  onFramesScrubbed: (e) =>
+    if e
+      # Gotta play all the sounds when scrubbing (but not when doing an immediate transition).
+      rising = @currentFrame > @lastFrame
+      actualCurrentFrame = @currentFrame
+      tempFrame = if rising then Math.ceil(@lastFrame) else Math.floor(@lastFrame)
+      while true  # temporary fix to stop cacophony
+        break if rising and tempFrame > actualCurrentFrame
+        break if (not rising) and tempFrame < actualCurrentFrame
+        @currentFrame = tempFrame
+        frame = @world.getFrame(@getCurrentFrame())
+        frame.restoreState()
+        for thangID, sprite of @spriteBoss.sprites
+          sprite.playSounds false, Math.max(0.05, Math.min(1, 1 / @scrubbingPlaybackSpeed))
+        tempFrame += if rising then 1 else -1
+      @currentFrame = actualCurrentFrame
 
-    # TODO: are these needed, or perhaps do they duplicate things?
+    @restoreWorldState()
     @spriteBoss.update true
     @onFrameChanged()
 
@@ -325,7 +326,7 @@ module.exports = Surface = class Surface extends CocoClass
   onNewWorld: (event) ->
     return unless event.world.name is @world.name
     @casting = false
-    
+
     # 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
@@ -493,14 +494,23 @@ module.exports = Surface = class Surface extends CocoClass
   tick: (e) =>
     # seems to be a bug where only one object can register with the Ticker...
     oldFrame = @currentFrame
+    oldWorldFrame = Math.floor oldFrame
     while true
       Dropper.tick()
       @trailmaster.tick() if @trailmaster
       # Skip some frame updates unless we're playing and not at end (or we haven't drawn much yet)
       frameAdvanced = (@playing and @currentFrame < @world.totalFrames) or @totalFramesDrawn < 2
       @currentFrame += @world.frameRate / @frameRate if frameAdvanced
-      @updateSpriteSounds() if frameAdvanced
+      newWorldFrame = Math.floor @currentFrame
+      worldFrameAdvanced = newWorldFrame isnt oldWorldFrame
+      if worldFrameAdvanced
+        # Only restore world state when it will correspond to an integer WorldFrame, not interpolated frame.
+        @restoreWorldState()
+        oldWorldFrame = newWorldFrame
       break unless Dropper.drop()
+    if frameAdvanced and not worldFrameAdvanced
+      # We didn't end the above loop on an integer frame, so do the world state update.
+      @restoreWorldState()
 
     # these are skipped for dropped frames
     @updateState @currentFrame isnt oldFrame
@@ -513,7 +523,7 @@ module.exports = Surface = class Surface extends CocoClass
       Backbone.Mediator.publish('surface:mouse-' + (if mib then "over" else "out"), {})
       @mouseInBounds = mib
 
-  updateSpriteSounds: ->
+  restoreWorldState: ->
     @world.getFrame(@getCurrentFrame()).restoreState()
     current = Math.max(0, Math.min(@currentFrame, @world.totalFrames - 1))
     if current - Math.floor(current) > 0.01
@@ -523,7 +533,7 @@ module.exports = Surface = class Surface extends CocoClass
     @spriteBoss.updateSounds()
 
   updateState: (frameChanged) ->
-    # world state must have been restored in @updateSpriteSounds
+    # world state must have been restored in @restoreWorldState
     @camera.updateZoom()
     @spriteBoss.update frameChanged unless @casting
     @dimmer?.setSprites @spriteBoss.sprites

From 6db8051122f85e34c8452ff1da82009323975636 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Fri, 28 Feb 2014 11:38:41 -0800
Subject: [PATCH 3/6] Only show action timeline when 'action' is in the
 hudProperties.

---
 app/views/play/level/hud_view.coffee | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/app/views/play/level/hud_view.coffee b/app/views/play/level/hud_view.coffee
index cc62fb047..bb49e2a70 100644
--- a/app/views/play/level/hud_view.coffee
+++ b/app/views/play/level/hud_view.coffee
@@ -145,7 +145,9 @@ module.exports = class HUDView extends View
 
   createActions: ->
     actions = @$el.find('.thang-actions tbody').empty()
-    return unless @thang.world and not _.isEmpty @thang.actions
+    showActions = @thang.world and not _.isEmpty(@thang.actions) and 'action' in @thang.hudProperties ? []
+    @$el.find('.thang-actions').toggle showActions
+    return unless showActions
     @buildActionTimespans()
     for actionName, action of @thang.actions
       actions.append @createActionElement(actionName)

From 52903cc0deae2962cda1523d9fab55fa0d8cbf80 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Fri, 28 Feb 2014 12:43:31 -0800
Subject: [PATCH 4/6] Using String and Array docs when appropriate now.

---
 app/views/play/level/tome/spell_palette_view.coffee | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/app/views/play/level/tome/spell_palette_view.coffee b/app/views/play/level/tome/spell_palette_view.coffee
index e045d1842..f4c708a18 100644
--- a/app/views/play/level/tome/spell_palette_view.coffee
+++ b/app/views/play/level/tome/spell_palette_view.coffee
@@ -44,15 +44,16 @@ module.exports = class SpellPaletteView extends View
     allDocs = {}
     for lc in lcs
       for doc in (lc.get('propertyDocumentation') ? [])
-        allDocs[doc.name] ?= []
-        allDocs[doc.name].push doc
+        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
 
     propStorage =
       'this': 'programmableProperties'
       more: 'moreProgrammableProperties'
       Math: 'programmableMathProperties'
+      Array: 'programmableArrayProperties'
+      String: 'programmableStringProperties'
       Vector: 'programmableVectorProperties'
       snippets: 'programmableSnippets'
     count = 0
@@ -66,10 +67,10 @@ module.exports = class SpellPaletteView extends View
     @entries = []
     for owner, props of propGroups
       for prop in props
-        doc = _.find (allDocs[prop] ? []), (doc) ->
+        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
+        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) ->

From ee2a60d246257bbc6e39c3d7600d06505696a93e Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Fri, 28 Feb 2014 13:22:19 -0800
Subject: [PATCH 5/6] Updated README to point to GSoC and ChallengePost.

---
 README.md | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/README.md b/README.md
index 9ed94553b..192b6ff31 100644
--- a/README.md
+++ b/README.md
@@ -19,3 +19,9 @@ Whether you're novice or pro, the CodeCombat team is ready to help you implement
 ### [License](https://github.com/codecombat/codecombat/blob/master/LICENSE)
 
 [MIT](https://github.com/codecombat/codecombat/blob/master/LICENSE) for the code, and [CC-BY](http://codecombat.com/legal) for the art and music. Please also [sign the CodeCombat contributor license agreement](http://codecombat.com/cla) so we can accept your pull requests. It is easy.
+
+----------
+
+[![](https://dl.dropboxusercontent.com/u/138899/GitHub%20Wikis/challengepost.png)](http://codecombat.challengepost.com/?utm_source-github&utm_medium-oswidget&utm_campaign-codecombat)
+
+[![](http://1-ps.googleusercontent.com/x/s.google-melange.appspot.com/www.google-melange.com/soc/content/2-1-20140225/images/gsoc/logo/920x156xbanner-gsoc2014.png.pagespeed.ic.gdr4t3Igca.png)](http://www.google-melange.com/gsoc/homepage/google/gsoc2014)
\ No newline at end of file

From 24b25b2d274224f80874b269b250f2ea1f9c53f5 Mon Sep 17 00:00:00 2001
From: Michael Schmatz <schmatz@umich.edu>
Date: Fri, 28 Feb 2014 13:50:48 -0800
Subject: [PATCH 6/6] Replaced project-dota with level id and major version

---
 server/queues/scoring.coffee | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/server/queues/scoring.coffee b/server/queues/scoring.coffee
index 40a6b2f21..08f0794d0 100644
--- a/server/queues/scoring.coffee
+++ b/server/queues/scoring.coffee
@@ -181,7 +181,8 @@ findNearestBetterSessionID = (sessionTotalScore, opponentSessionTotalScore, oppo
       $gt:opponentSessionTotalScore + 0.5
     _id: 
       $ne: opponentSessionID
-    levelID: "project-dota"
+    "level.original": "52d97ecd32362bc86e004e87"
+    "level.majorVersion": 0
     submitted: true
     submittedCode:
       $exists: true
@@ -280,7 +281,8 @@ updateSessionToSubmit = (sessionToUpdate, callback) ->
 fetchInitialSessionsToRankAgainst = (opposingTeam, callback) ->
   console.log "Fetching sessions to rank against for opposing team #{opposingTeam}"
   findParameters =
-    levelID: "project-dota"
+    "level.original": "52d97ecd32362bc86e004e87"
+    "level.majorVersion": 0
     submitted: true
     submittedCode:
       $exists: true