From 23e60116d52ffa04fa13c5413e1f3629ea73a55b Mon Sep 17 00:00:00 2001
From: Matt Lott <mattlott@live.com>
Date: Sun, 30 Nov 2014 22:11:36 -0800
Subject: [PATCH 01/10] A/B test hiding locked levels in world map

---
 app/models/User.coffee                 |  9 +++++
 app/templates/play/world-map-view.jade | 50 +++++++++++++-------------
 app/views/play/WorldMapView.coffee     |  6 +++-
 3 files changed, 40 insertions(+), 25 deletions(-)

diff --git a/app/models/User.coffee b/app/models/User.coffee
index 3fec2a995..79c45e4fc 100644
--- a/app/models/User.coffee
+++ b/app/models/User.coffee
@@ -124,6 +124,15 @@ module.exports = class User extends CocoModel
     application.tracker.identify gemPromptGroup: @gemPromptGroup unless me.isAdmin()
     @gemPromptGroup
 
+  getHideLockedLevelsGroup: ->
+    return @hideLockedLevelsGroup if @hideLockedLevelsGroup
+    group = if me.isAdmin() then 0 else me.get('testGroupNumber') % 2
+    @hideLockedLevelsGroup = switch group
+      when 0 then 'show'
+      when 1 then 'hide'
+    application.tracker.identify hideLockedLevelsGroup: @hideLockedLevelsGroup unless me.isAdmin()
+    @hideLockedLevelsGroup
+
 tiersByLevel = [-1, 0, 0.05, 0.14, 0.18, 0.32, 0.41, 0.5, 0.64, 0.82, 0.91, 1.04, 1.22, 1.35, 1.48, 1.65, 1.78, 1.96, 2.1, 2.24, 2.38, 2.55, 2.69, 2.86, 3.03, 3.16, 3.29, 3.42, 3.58, 3.74, 3.89, 4.04, 4.19, 4.32, 4.47, 4.64, 4.79, 4.96,
   5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 10, 10.5, 11, 11.5, 12, 12.5, 13, 13.5, 14, 14.5, 15
 ]
diff --git a/app/templates/play/world-map-view.jade b/app/templates/play/world-map-view.jade
index a11003fff..1c2a12d06 100644
--- a/app/templates/play/world-map-view.jade
+++ b/app/templates/play/world-map-view.jade
@@ -10,30 +10,32 @@
     if !level.hidden
       - var next = level.id == nextLevel || (!seenNext && levelStatusMap[level.id] != "complete" && !level.locked && !level.disabled && (!level.practice || me.getBranchingGroup() == 'all-practice'));
       - seenNext = seenNext || next;
-      div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
-        a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
-      div(style="left: #{level.x}%; bottom: #{level.y}%", class="level-shadow" + (next ? " next" : "") + " " + levelStatusMap[level.id] || "")
-      .level-info-container(data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
-        div(class="level-info " + (levelStatusMap[level.id] || ""))
-          h3= level.name + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
-          .level-description= level.description
-          if level.disabled
-            p
-              span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week.
-              a.spr(href="/contribute/adventurer")
-                strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer
-              span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels.
-              
-          - var playCount = levelPlayCountMap[level.id]
-          if playCount && playCount.sessions > 20
-            div
-              span.spr #{playCount.sessions}
-              span(data-i18n="play.players") players
-              span.spr , #{Math.round(playCount.playtime / 3600)}
-              span(data-i18n="play.hours_played") hours played
-          .campaign-label(style="color: #{campaign.color}")= campaign.name
-        if isIPadApp && !level.disabled && !level.locked
-          button.btn.btn-success.btn-lg.start-level(data-i18n="common.play") Play
+      //- A/B Test hiding locked levels
+      if !hideLockedLevels || !level.locked
+        div(style="left: #{level.x}%; bottom: #{level.y}%; background-color: #{level.color}", class="level" + (next ? " next" : "") + (level.disabled ? " disabled" : "") + (level.locked ? " locked" : "") + " " + levelStatusMap[level.id] || "", data-level-id=level.id, title=level.name + (level.disabled ? ' (Coming Soon to Adventurers)' : ''))
+          a(href=level.type == 'hero' ? '#' : level.disabled ? "/play" : "/play/#{level.levelPath || 'level'}/#{level.id}", disabled=level.disabled, data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
+        div(style="left: #{level.x}%; bottom: #{level.y}%", class="level-shadow" + (next ? " next" : "") + " " + levelStatusMap[level.id] || "")
+        .level-info-container(data-level-id=level.id, data-level-path=level.levelPath || 'level', data-level-name=level.name)
+          div(class="level-info " + (levelStatusMap[level.id] || ""))
+            h3= level.name + (level.disabled ? " (Coming soon!)" : (level.locked ? " (Locked)" : ""))
+            .level-description= level.description
+            if level.disabled
+              p
+                span.spr(data-i18n="play.awaiting_levels_adventurer_prefix") We release five levels per week.
+                a.spr(href="/contribute/adventurer")
+                  strong(data-i18n="play.awaiting_levels_adventurer") Sign up as an Adventurer
+                span.spl(data-i18n="play.awaiting_levels_adventurer_suffix") to be the first to play new levels.
+                
+            - var playCount = levelPlayCountMap[level.id]
+            if playCount && playCount.sessions > 20
+              div
+                span.spr #{playCount.sessions}
+                span(data-i18n="play.players") players
+                span.spr , #{Math.round(playCount.playtime / 3600)}
+                span(data-i18n="play.hours_played") hours played
+            .campaign-label(style="color: #{campaign.color}")= campaign.name
+          if isIPadApp && !level.disabled && !level.locked
+            button.btn.btn-success.btn-lg.start-level(data-i18n="common.play") Play
   if mapType === 'dungeon' && forestIsAvailable
     a#forest-link.glyphicon.glyphicon-share-alt.campaign-switch(href="/play/forest", data-i18n="[title]play.campaign_forest")
   if mapType === 'forest'
diff --git a/app/views/play/WorldMapView.coffee b/app/views/play/WorldMapView.coffee
index c53acde7e..596236133 100644
--- a/app/views/play/WorldMapView.coffee
+++ b/app/views/play/WorldMapView.coffee
@@ -69,7 +69,9 @@ module.exports = class WorldMapView extends RootView
     @hadEverChosenHero = me.get('heroConfig')?.thangType
     @listenTo me, 'change:purchased', -> @renderSelectors('#gems-count')
     @listenTo me, 'change:spent', -> @renderSelectors('#gems-count')
-    window.tracker?.trackEvent 'Loaded World Map', category: 'World Map', ['Google Analytics']
+    # A/B Test hiding locked levels
+    # window.tracker?.trackEvent 'Loaded World Map', category: 'World Map', ['Google Analytics']
+    window.tracker?.trackEvent 'Loaded World Map', category: 'World Map', hideLockedLevelsGroup: me.getHideLockedLevelsGroup()
 
     # If it's a new player who didn't appear to come from Hour of Code, we register her here without setting the hourOfCode property.
     elapsed = (new Date() - new Date(me.get('dateCreated')))
@@ -135,6 +137,8 @@ module.exports = class WorldMapView extends RootView
     context.mapType = _.string.slugify @terrain
     context.nextLevel = @nextLevel
     context.forestIsAvailable = @startedForestLevel or '541b67f71ccc8eaae19f3c62' in (me.get('earned')?.levels or [])
+    # A/B Test hiding locked levels
+    context.hideLockedLevels = me.getHideLockedLevelsGroup() is 'hide'
     context
 
   afterRender: ->

From 4d1130a9c67285813163189a16467511e5bef17b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bj=C3=B6rn=20Kimminich?= <bjoern.kimminich@gmx.de>
Date: Mon, 1 Dec 2014 14:00:36 +0100
Subject: [PATCH 02/10] Update de-DE.coffee

---
 app/locale/de-DE.coffee | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/app/locale/de-DE.coffee b/app/locale/de-DE.coffee
index cf3bd9e1b..44fc06618 100644
--- a/app/locale/de-DE.coffee
+++ b/app/locale/de-DE.coffee
@@ -680,9 +680,9 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     diplomat_launch_url: "Launch im Oktober"
     diplomat_introduction_suf: "ist das es ein großes Interesse an CodeCombat in anderen Ländern gibt! Wir stellen eine Truppe von Übersetzern zusammen, die gewillt sind einen Satz Wörten in einen anderen Satz Wörter umzuwandeln um CodeCombat der Welt so zugänglich wie möglich zu machen. Wenn du es magst eine Vorschau von zukünftigem Content zu erhalten und diese Level so schnell wie möglich deinen Landsleuten zur Verfügung zu stellen, dann ist diese Klasse vielleicht für dich."
 #    diplomat_attribute_1: "Fluency in English and the language you would like to translate to. When conveying complicated ideas, it's important to have a strong grasp in both!"
-#    diplomat_i18n_page_prefix: "You can start translating our levels by going to our"
-#    diplomat_i18n_page: "translations page"
-#    diplomat_i18n_page_suffix: ", or our interface and website on GitHub."
+    diplomat_i18n_page_prefix: "Du kanns anfangen unsere Levels zu übersetzen, indem du auf unsere"
+    diplomat_i18n_page: "Übersetzungs-Seite"
+    diplomat_i18n_page_suffix: "gehst, oder unsere Schnittstelle und Webseite bei GitHub benutzt."
     diplomat_join_pref_github: "Finde deine Sprachdatei "
     diplomat_github_url: "bei GitHub"
     diplomat_join_suf_github: ", editiere sie online und reiche einen Pull Request ein. Außerdem, hake die Checkbox unten an um über neue Entwicklungen bei der Internationalisierung auf dem laufenden zu bleiben!"
@@ -844,7 +844,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     thang_names: "Thang Namen"
     files: "Dateien"
     top_simulators: "Top Simulatoren"
-#    source_document: "Source Document"
+    source_document: "Quelldokument"
     document: "Dokument"
     sprite_sheet: "Sprite Sheet"
     employers: "Arbeitgeber"
@@ -1086,7 +1086,7 @@ module.exports = nativeDescription: "Deutsch (Deutschland)", englishDescription:
     player_code: "Spieler Code"
 
   employers:
-#    deprecation_warning_title: "Sorry, CodeCombat is not recruiting right now."
+    deprecation_warning_title: "Leider sucht CodeCombat derzeit keine neuen Mitarbeiter."
 #    deprecation_warning: "We are focusing on beginner levels instead of finding expert developers for the time being."
     hire_developers_not_credentials: "Stellen Sie Entwickler ein, nicht Qualifikationen." # We are not actively recruiting right now, so there's no need to add new translations for the rest of this section.
     get_started: "Legen Sie los"

From f9b239ded78466700232a686ff2bcfbe4e649273 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Mon, 1 Dec 2014 08:18:38 -0800
Subject: [PATCH 03/10] Fixed #1820.

---
 app/templates/account/job-profile-view.jade                | 2 +-
 app/templates/cla.jade                                     | 2 +-
 app/templates/contribute/contributor_signup_anonymous.jade | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/app/templates/account/job-profile-view.jade b/app/templates/account/job-profile-view.jade
index 404de1521..f0dae7d07 100644
--- a/app/templates/account/job-profile-view.jade
+++ b/app/templates/account/job-profile-view.jade
@@ -5,7 +5,7 @@ block content
     div(class="job-profile-container")
       h1#login-message 
         |Please 
-        a.auth-button login 
+        a.login-button login 
         | to view this profile.
   else if user.loaded
     if allowedToEditJobProfile
diff --git a/app/templates/cla.jade b/app/templates/cla.jade
index cdf375968..bda7e1f09 100644
--- a/app/templates/cla.jade
+++ b/app/templates/cla.jade
@@ -83,7 +83,7 @@
     p
       strong You must be signed in to sign this agreement.
     
-    button.btn.btn-primary.auth-button
+    button.btn.btn-primary.login-button
       span(data-i18n="login.log_in") Log In
       span.spr.spl /
       span(data-i18n="login.sign_up") Create Account
diff --git a/app/templates/contribute/contributor_signup_anonymous.jade b/app/templates/contribute/contributor_signup_anonymous.jade
index 4fc05a4bb..ae5263dc6 100644
--- a/app/templates/contribute/contributor_signup_anonymous.jade
+++ b/app/templates/contribute/contributor_signup_anonymous.jade
@@ -7,7 +7,7 @@ if me.attributes.anonymous
       | To subscribe for class emails, you'll need to be logged in first.
 
     strong.spl
-      a.auth-button
+      a.signup-button
         span(data-i18n="login.log_in") Log In
         span.spr.spl /
         span(data-i18n="login.sign_up") Create Account
\ No newline at end of file

From 43ac0e857221693d9bcaffb957153787da97be46 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Mon, 1 Dec 2014 10:35:45 -0800
Subject: [PATCH 04/10] Proxied 'lib/auth' to 'core/auth' for the iPad which
 can't be updated so quickly.

---
 app/core/ModuleLoader.coffee | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/core/ModuleLoader.coffee b/app/core/ModuleLoader.coffee
index 8ab2d7f43..f27a2d183 100644
--- a/app/core/ModuleLoader.coffee
+++ b/app/core/ModuleLoader.coffee
@@ -20,6 +20,7 @@ module.exports = ModuleLoader = class ModuleLoader extends CocoClass
     wrapped = _.wrap window.require, (func, name, loaderPath) ->
       # vendor libraries aren't actually wrapped with common.js, so short circuit those requires
       return {} if _.string.startsWith(name, 'vendor/')
+      name = 'core/auth' if name is 'lib/auth' # proxy for iPad until it's been updated to use the new, refactored location. TODO: remove this
       return func(name, loaderPath)
     _.extend wrapped, window.require # for functions like 'list'
     window.require = wrapped

From 57b154810b4f8b56214f678d34c7bfc97fbd35be Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Mon, 1 Dec 2014 10:45:19 -0800
Subject: [PATCH 05/10] Added a placeholder me for iPad which inspects the
 object once the page loads.

---
 app/assets/main.html | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/app/assets/main.html b/app/assets/main.html
index 2e73c51da..5b983a7a3 100644
--- a/app/assets/main.html
+++ b/app/assets/main.html
@@ -34,9 +34,15 @@
   <script src="/javascripts/aether.js" defer></script>
   <script src="/javascripts/app.js" defer></script> <!-- it's all Backbone! -->
   <script>
+    
+    // Placeholder for iPad, which inspects the user object at the bottom of an injected page.
+    window.userObject = "userObjectTag";
+    window.me = {
+      get: function(attribute) { return window.userObject[attribute]; }
+    }
+
     onLoad = function() {
       FastClick.attach(document.body);
-      window.userObject = "userObjectTag";
       require('core/initialize');
     }
   </script>

From 1b12eee3ac4e161b8805ffe35530dbefb2521a79 Mon Sep 17 00:00:00 2001
From: Scott Erickson <sderickson@gmail.com>
Date: Mon, 1 Dec 2014 10:49:38 -0800
Subject: [PATCH 06/10] Fixed signup in victory modals and ContributeClassView.

---
 app/templates/play/level/modal/hero-victory-modal.jade | 2 +-
 app/templates/play/level/modal/victory.jade            | 2 +-
 app/views/contribute/ContributeClassView.coffee        | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/app/templates/play/level/modal/hero-victory-modal.jade b/app/templates/play/level/modal/hero-victory-modal.jade
index 463b69077..f0d00ea4c 100644
--- a/app/templates/play/level/modal/hero-victory-modal.jade
+++ b/app/templates/play/level/modal/hero-victory-modal.jade
@@ -50,7 +50,7 @@ block modal-body-content
 block modal-footer-content
   if me.get('anonymous')
     p.sign-up-poke.hide
-      button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="modal/SignupModal", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
+      button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="core/AuthModal", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
       span(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
 
   div#totals.pull-left
diff --git a/app/templates/play/level/modal/victory.jade b/app/templates/play/level/modal/victory.jade
index be6a4ab51..d2fbd3e70 100644
--- a/app/templates/play/level/modal/victory.jade
+++ b/app/templates/play/level/modal/victory.jade
@@ -23,7 +23,7 @@ block modal-footer-content
     a.btn.btn-primary(href="/", data-dismiss="modal", data-i18n="play_level.victory_go_home") Go Home
   if me.get('anonymous')
     p.sign-up-poke
-      button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="modal/SignupModal", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
+      button.btn.btn-success.sign-up-button.btn-large(data-toggle="coco-modal", data-target="core/AuthModal", data-i18n="play_level.victory_sign_up") Sign Up to Save Progress
       span(data-i18n="play_level.victory_sign_up_poke") Want to save your code? Create a free account!
     p.clearfix
   else
diff --git a/app/views/contribute/ContributeClassView.coffee b/app/views/contribute/ContributeClassView.coffee
index 18be62776..8bead529b 100644
--- a/app/views/contribute/ContributeClassView.coffee
+++ b/app/views/contribute/ContributeClassView.coffee
@@ -1,4 +1,4 @@
-SignupModalView = require 'views/modal/SignupModal'
+AuthModal = require 'views/core/AuthModal'
 RootView = require 'views/core/RootView'
 {me} = require 'core/auth'
 contributorSignupAnonymousTemplate = require 'templates/contribute/contributor_signup_anonymous'
@@ -37,7 +37,7 @@ module.exports = class ContributeClassView extends RootView
 
     me.setEmailSubscription subscription+'News', checked
     me.patch()
-    @openModalView new SignupModalView() if me.get 'anonymous'
+    @openModalView new AuthModal() if me.get 'anonymous'
     el.parent().find('.saved-notification').finish().show('fast').delay(3000).fadeOut(2000)
 
   contributors: []

From 1e4d6b3bd5b74360209c5f3de02ffb14e165582a Mon Sep 17 00:00:00 2001
From: Imperadeiro98 <Imperadeiro98@users.noreply.github.com>
Date: Mon, 1 Dec 2014 20:27:52 +0000
Subject: [PATCH 07/10] Update pt-PT.coffee

---
 app/locale/pt-PT.coffee | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/locale/pt-PT.coffee b/app/locale/pt-PT.coffee
index 3436ec412..215145295 100644
--- a/app/locale/pt-PT.coffee
+++ b/app/locale/pt-PT.coffee
@@ -6,7 +6,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
     play: "Jogar" # The big play button that just starts playing a level
     old_browser: "Ups, o teu navegador é demasiado antigo para que o CodeCombat funcione. Desculpa!" # Warning that shows up on really old Firefox/Chrome/Safari
     old_browser_suffix: "Mesmo assim podes tentar, mas provavelmente não irá funcionar."
-#    ipad_browser: "Bad news: CodeCombat doesn't run on iPad in the browser. Good news: our native iPad app is awaiting Apple approval."
+    ipad_browser: "Más notícias: o CodeCombat não funciona no navegador do iPad. Boas notícias: a nossa aplicação nativa para iPad está à espera da aprovação da Apple."
     campaign: "Campanha"
     for_beginners: "Para Iniciantes"
     multiplayer: "Multijogador" # Not currently shown on home page
@@ -915,7 +915,7 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
     cla_url: "CLA"
     contributor_description_suffix: "com o qual deves concordar antes de contribuir."
     code_title: "Código - MIT"
-#    code_description_prefix: "All code owned by CodeCombat or hosted on codecombat.com, both in the GitHub repository or in the codecombat.com database, is licensed under the"
+    code_description_prefix: "Todo o código do CodeCombat ou hospedado em codecombat.com, tanto no repositório do GitHub como both in the GitHub repository or in the codecombat.com database, is licensed under the"
     mit_license_url: "licença do MIT"
 #    code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
     art_title: "Arte/Música - Creative Commons "

From 6036d2bd741b6e0d8b6760a24a4e74c177c87bc2 Mon Sep 17 00:00:00 2001
From: Imperadeiro98 <Imperadeiro98@users.noreply.github.com>
Date: Mon, 1 Dec 2014 20:45:29 +0000
Subject: [PATCH 08/10] Update pt-PT.coffee

---
 app/locale/pt-PT.coffee | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/locale/pt-PT.coffee b/app/locale/pt-PT.coffee
index 215145295..053815b8e 100644
--- a/app/locale/pt-PT.coffee
+++ b/app/locale/pt-PT.coffee
@@ -915,9 +915,9 @@ module.exports = nativeDescription: "Português (Portugal)", englishDescription:
     cla_url: "CLA"
     contributor_description_suffix: "com o qual deves concordar antes de contribuir."
     code_title: "Código - MIT"
-    code_description_prefix: "Todo o código do CodeCombat ou hospedado em codecombat.com, tanto no repositório do GitHub como both in the GitHub repository or in the codecombat.com database, is licensed under the"
+    code_description_prefix: "Todo o código do CodeCombat ou hospedado em codecombat.com, tanto no repositório do GitHub como na base de dados de codecombat.com, está resguardado sob a"
     mit_license_url: "licença do MIT"
-#    code_description_suffix: "This includes all code in Systems and Components that are made available by CodeCombat for the purpose of creating levels."
+    code_description_suffix: "Isto inclui todo o código dentro dos Sistemas e dos Componentes, o qual é disponibilizado pelo CodeCombat para a criação de níveis."
     art_title: "Arte/Música - Creative Commons "
     art_description_prefix: "Todos os conteúdos comuns estão disponíveis à luz da"
     cc_license_url: "Licença 'Creative Commons Attribution 4.0 International'"

From ca6c95b0f7d658af9e4ff9bd752132e8fafcea2b Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Mon, 1 Dec 2014 13:46:55 -0800
Subject: [PATCH 09/10] Migrated getting nextLevelURL to the new campaign
 organization.

---
 app/lib/LevelLoader.coffee                |  2 +-
 app/views/play/level/PlayLevelView.coffee | 19 +++----------------
 2 files changed, 4 insertions(+), 17 deletions(-)

diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
index 92c31e165..40743699c 100644
--- a/app/lib/LevelLoader.coffee
+++ b/app/lib/LevelLoader.coffee
@@ -176,7 +176,7 @@ module.exports = class LevelLoader extends CocoClass
     for obj in objUniq articleVersions
       url = "/db/article/#{obj.original}/version/#{obj.majorVersion}"
       @maybeLoadURL url, Article, 'article'
-    if obj = @level.get 'nextLevel'
+    if obj = @level.get 'nextLevel'  # TODO: update to get next level from campaigns, not this old property
       url = "/db/level/#{obj.original}/version/#{obj.majorVersion}"
       @maybeLoadURL url, Level, 'level'
 
diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee
index 06d744abf..13a7464f9 100644
--- a/app/views/play/level/PlayLevelView.coffee
+++ b/app/views/play/level/PlayLevelView.coffee
@@ -417,7 +417,6 @@ module.exports = class PlayLevelView extends RootView
   onShowVictory: (e) ->
     $('#level-done-button').show() unless @level.get('type', true) in ['hero', 'hero-ladder', 'hero-coop']
     @showVictory() if e.showModal
-    setTimeout(@preloadNextLevel, 3000)
     return if @victorySeen
     @victorySeen = true
     victoryTime = (new Date()) - @loadEndTime
@@ -457,14 +456,10 @@ module.exports = class PlayLevelView extends RootView
       viewClass: PlayLevelView,
       viewArgs: [{supermodel: if @hasReceivedMemoryWarning then null else @supermodel}, nextLevelID]}
 
-  getNextLevel: ->
-    return null unless nextLevelOriginal = @level.get('nextLevel')?.original
-    levels = @supermodel.getModels(Level)
-    return l for l in levels when l.get('original') is nextLevelOriginal
-
   getNextLevelID: ->
-    return null unless nextLevel = @getNextLevel()
-    nextLevelID = nextLevel.get('slug') or nextLevel.id
+    for campaign in require('views/play/WorldMapView').campaigns
+      for level in campaign.levels
+        return level.nextLevels?.continue if level.id is @level.get('slug')
 
   getNextLevelURL: ->
     return null unless @getNextLevelID()
@@ -483,14 +478,6 @@ module.exports = class PlayLevelView extends RootView
       @bus.removeFirebaseData =>
         @bus.disconnect()
 
-  preloadNextLevel: =>
-    # TODO: Loading models in the middle of gameplay causes stuttering. Most of the improvement in loading time is simply from passing the supermodel from this level to the next, but if we can find a way to populate the level early without it being noticeable, that would be even better.
-#    return if @destroyed
-#    return if @preloaded
-#    nextLevel = @getNextLevel()
-#    @supermodel.populateModel nextLevel
-#    @preloaded = true
-
   onSessionWillSave: (e) ->
     # Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
     #@saveScreenshot e.session

From ffcfec0a6a72f4b8f1db011638f8f654c3985381 Mon Sep 17 00:00:00 2001
From: Nick Winter <livelily@gmail.com>
Date: Mon, 1 Dec 2014 13:53:17 -0800
Subject: [PATCH 10/10] Trying to shut the server up about some anonymous user
 who is simulating.

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

diff --git a/server/queues/scoring.coffee b/server/queues/scoring.coffee
index 4a4c78687..736a60450 100644
--- a/server/queues/scoring.coffee
+++ b/server/queues/scoring.coffee
@@ -225,7 +225,7 @@ module.exports.recordTwoGames = (req, res) ->
     updateSessions.bind(yetiGuru)
     indexNewScoreArray.bind(yetiGuru)
     addMatchToSessions.bind(yetiGuru)
-    updateUserSimulationCounts.bind(yetiGuru, req.user._id)
+    updateUserSimulationCounts.bind(yetiGuru, req.user?._id)
   ], (err, successMessageObject) ->
     if err? then return errors.serverError res, "There was an error recording the single game:#{err}"
     sendResponseObject req, res, {'message': 'The single game was submitted successfully!'}
@@ -450,7 +450,7 @@ module.exports.processTaskResult = (req, res) ->
       updateSessions.bind(yetiGuru)
       indexNewScoreArray.bind(yetiGuru)
       addMatchToSessions.bind(yetiGuru)
-      updateUserSimulationCounts.bind(yetiGuru, req.user._id)
+      updateUserSimulationCounts.bind(yetiGuru, req.user?._id)
       determineIfSessionShouldContinueAndUpdateLog.bind(yetiGuru)
       findNearestBetterSessionID.bind(yetiGuru)
       addNewSessionsToQueue.bind(yetiGuru)
@@ -615,6 +615,7 @@ updateUserSimulationCounts = (reqUserID, callback) ->
       callback null
 
 incrementUserSimulationCount = (userID, type, callback) =>
+  return callback null unless userID
   inc = {}
   inc[type] = 1
   User.update {_id: userID}, {$inc: inc}, (err, affected) ->