diff --git a/app/core/Tracker.coffee b/app/core/Tracker.coffee
index 4294bc6ce..9e07f41d9 100644
--- a/app/core/Tracker.coffee
+++ b/app/core/Tracker.coffee
@@ -100,7 +100,7 @@ module.exports = class Tracker
eventObject["user"] = me.id
dataToSend = JSON.stringify eventObject
# console.log dataToSend if debugAnalytics
- $.post("http://analytics.codecombat.com/analytics", dataToSend).fail ->
+ $.post("#{window.location.protocol or 'http:'}//analytics.codecombat.com/analytics", dataToSend).fail ->
console.error "Analytics post failed!"
else
request = @supermodel.addRequestResource 'log_event', {
diff --git a/app/core/storage.coffee b/app/core/storage.coffee
index 21e8d59a9..f31e8b300 100644
--- a/app/core/storage.coffee
+++ b/app/core/storage.coffee
@@ -1,4 +1,6 @@
-module.exports.load = (key) ->
+# Pass false for fromCache to fetch keys that have been stored outside of lscache.
+module.exports.load = (key, fromCache=true) ->
+ return lscache.get key if fromCache
s = localStorage.getItem(key)
return null unless s
try
@@ -8,8 +10,17 @@ module.exports.load = (key) ->
console.warn('error loading from storage', key)
return null
-module.exports.save = (key, value) ->
- s = JSON.stringify(value)
- localStorage.setItem(key, s)
+# Pass 0 for expirationInMinutes to persist it as long as possible outside of lscache expiration.
+module.exports.save = (key, value, expirationInMinutes) ->
+ expirationInMinutes ?= 7 * 24 * 60
+ if expirationInMinutes
+ lscache.set key, value, expirationInMinutes
+ else
+ localStorage.setItem key, JSON.stringify(value)
-module.exports.remove = (key) -> localStorage.removeItem key
+# Pass false for fromCache to remove keys that have been stored outside of lscache.
+module.exports.remove = (key, fromCache=true) ->
+ if fromCache
+ lscache.remove key
+ else
+ localStorage.removeItem key
diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee
index 37d304f5d..18e175f72 100644
--- a/app/lib/world/world.coffee
+++ b/app/lib/world/world.coffee
@@ -70,9 +70,9 @@ module.exports = class World
setThang: (thang) ->
thang.stateChanged = true
for old, i in @thangs
- console.error 'world trying to set', thang, 'over', old unless old? and thang?
if old.id is thang.id
@thangs[i] = thang
+ break
@thangMap[thang.id] = thang
thangDialogueSounds: (startFrame=0) ->
@@ -102,7 +102,9 @@ module.exports = class World
if @realTime and not @countdownFinished
@realTimeSpeedFactor = 1
unless @showsCountdown
- if @levelID in ['village-guard', 'thornbush-farm', 'back-to-back', 'ogre-encampment', 'woodland-cleaver', 'shield-rush', 'peasant-protection', 'munchkin-swarm', 'munchkin-harvest', 'swift-dagger', 'shrapnel', 'arcane-ally', 'touch-of-death', 'bonemender']
+ if @levelID in ['woodland-cleaver', 'village-guard', 'shield-rush']
+ @realTimeSpeedFactor = 2
+ else if @levelID in ['thornbush-farm', 'back-to-back', 'ogre-encampment', 'peasant-protection', 'munchkin-swarm', 'munchkin-harvest', 'swift-dagger', 'shrapnel', 'arcane-ally', 'touch-of-death', 'bonemender']
@realTimeSpeedFactor = 3
if @showsCountdown
return setTimeout @finishCountdown(continueLaterFn), REAL_TIME_COUNTDOWN_DELAY
diff --git a/app/locale/en.coffee b/app/locale/en.coffee
index 5222b8a3d..3a9d6dea1 100644
--- a/app/locale/en.coffee
+++ b/app/locale/en.coffee
@@ -529,8 +529,6 @@
volume_label: "Volume"
music_label: "Music"
music_description: "Turn background music on/off."
- autorun_label: "Autorun"
- autorun_description: "Control automatic code execution."
editor_config: "Editor Config"
editor_config_title: "Editor Configuration"
editor_config_level_language_label: "Language for This Level"
diff --git a/app/schemas/models/user.coffee b/app/schemas/models/user.coffee
index 111ddab62..df9203abc 100644
--- a/app/schemas/models/user.coffee
+++ b/app/schemas/models/user.coffee
@@ -63,10 +63,10 @@ _.extend UserSchema.properties,
githubID: {type: 'integer', title: 'GitHub ID'}
gplusID: c.shortString({title: 'G+ ID'})
- wizardColor1: c.pct({title: 'Wizard Clothes Color'})
+ wizardColor1: c.pct({title: 'Wizard Clothes Color'}) # No longer used
volume: c.pct({title: 'Volume'})
music: { type: 'boolean' }
- autocastDelay: { type: 'integer' }
+ autocastDelay: { type: 'integer' } # No longer used
lastLevel: { type: 'string' }
heroConfig: c.HeroConfigSchema
diff --git a/app/templates/base.jade b/app/templates/base.jade
index 003471b53..92df03f01 100644
--- a/app/templates/base.jade
+++ b/app/templates/base.jade
@@ -73,7 +73,7 @@ block footer
.fb-like(data-href="https://www.facebook.com/codecombat", data-send="false", data-layout="button_count", data-width="350", data-show-faces="true", data-ref="coco_footer_#{fbRef}")
if !isIE
a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow
- iframe.github-star-button(src="http://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")
+ iframe.github-star-button(src="https://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")
#footer-credits
span
diff --git a/app/templates/play/level/modal/victory.jade b/app/templates/play/level/modal/victory.jade
index 49a6b896f..effaad52c 100644
--- a/app/templates/play/level/modal/victory.jade
+++ b/app/templates/play/level/modal/victory.jade
@@ -39,4 +39,4 @@ block modal-footer-content
.g-plusone(data-href="http://codecombat.com", data-size="medium")
.fb-like(data-href="https://www.facebook.com/codecombat", data-send="false", data-layout="button_count", data-width="350", data-show-faces="true", data-ref="coco_victory_#{fbRef}")
a.twitter-follow-button(href="https://twitter.com/CodeCombat", data-show-count="true", data-show-screen-name="false", data-dnt="true", data-align="right", data-i18n="nav.twitter_follow") Follow
- iframe.github-star-button(src="http://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")
+ iframe.github-star-button(src="https://ghbtns.com/github-btn.html?user=codecombat&repo=codecombat&type=watch&count=true", allowtransparency="true", frameborder="0", scrolling="0", width="110", height="20")
diff --git a/app/templates/play/menu/game-menu-modal.jade b/app/templates/play/menu/game-menu-modal.jade
index 689c41b53..ed7beae05 100644
--- a/app/templates/play/menu/game-menu-modal.jade
+++ b/app/templates/play/menu/game-menu-modal.jade
@@ -6,10 +6,11 @@
span.glyphicon.glyphicon-remove
ul#game-menu-nav.nav.nav-pills.nav-stacked
- li
- a#change-hero-tab
- span.glyphicon.glyphicon-user
- span(data-i18n='[title]game_menu.choose_hero_caption;play.change_hero')
+ if showsChooseHero
+ li
+ a#change-hero-tab
+ span.glyphicon.glyphicon-user
+ span(data-i18n='[title]game_menu.choose_hero_caption;play.change_hero')
for submenu, index in submenus
li(class=submenu === showTab ? "active" : "")
diff --git a/app/templates/play/menu/options-view.jade b/app/templates/play/menu/options-view.jade
index 5f43a07a8..451d960df 100644
--- a/app/templates/play/menu/options-view.jade
+++ b/app/templates/play/menu/options-view.jade
@@ -22,16 +22,6 @@
span(data-i18n="options.music_label") Music
span.help-block(data-i18n="options.music_description") Turn background music on/off.
- .form-group.select-group
- label.control-label(for="option-autorun-delay", data-i18n="options.autorun_label") Autorun
- select#option-autorun-delay.form-control(name="autorunDelay")
- option(value=1000, selected=(autorunDelay === 1000), data-i18n="common.delay_1_sec") 1 second
- option(value=3000, selected=(autorunDelay === 3000), data-i18n="common.delay_3_sec") 3 seconds
- option(value=5000, selected=(autorunDelay === 5000), data-i18n="common.delay_5_sec") 5 seconds
- option(value=90019001, selected=(autorunDelay === 90019001), data-i18n="common.manual") Manual
- span.help-block(data-i18n="options.autorun_description") Control automatic code execution.
-
-
img.hr(src="/images/pages/play/modal/hr.png", draggable="false")
h3(data-i18n="options.editor_config_title") Editor Configuration
diff --git a/app/views/HomeView.coffee b/app/views/HomeView.coffee
index ac14ba60c..718144ce3 100644
--- a/app/views/HomeView.coffee
+++ b/app/views/HomeView.coffee
@@ -51,5 +51,5 @@ module.exports = class HomeView extends RootView
me.set 'hourOfCode', true
me.patch()
# We may also insert the tracking pixel for everyone on the CampaignView so as to count directly-linked visitors.
- $('body').append($(''))
+ $('body').append($(''))
application.tracker?.trackEvent 'Hour of Code Begin'
diff --git a/app/views/ladder/LadderPlayModal.coffee b/app/views/ladder/LadderPlayModal.coffee
index 96ba9937a..d0c14ba17 100644
--- a/app/views/ladder/LadderPlayModal.coffee
+++ b/app/views/ladder/LadderPlayModal.coffee
@@ -37,6 +37,9 @@ module.exports = class LadderPlayModal extends ModalView
aceConfig.language = @$el.find('#tome-language').val()
me.set 'aceConfig', aceConfig
me.patch()
+ if @session
+ @session.set 'codeLanguage', aceConfig.language
+ @session.patch()
# PART 1: Load challengers from the db unless some are in the matches
startLoadingChallengersMaybe: ->
@@ -88,6 +91,7 @@ module.exports = class LadderPlayModal extends ModalView
ctx.teamID = @team
ctx.otherTeamID = @otherTeam
ctx.tutorialLevelExists = @tutorialLevelExists
+ ctx.language = @session?.get('codeLanguage') ? me.get('aceConfig')?.language ? 'python'
ctx.languages = [
{id: 'python', name: 'Python'}
{id: 'javascript', name: 'JavaScript'}
diff --git a/app/views/play/CampaignView.coffee b/app/views/play/CampaignView.coffee
index f8b981dd7..5797f11a1 100644
--- a/app/views/play/CampaignView.coffee
+++ b/app/views/play/CampaignView.coffee
@@ -108,7 +108,7 @@ module.exports = class CampaignView extends RootView
# 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')))
if not trackedHourOfCode and not me.get('hourOfCode') and elapsed < 5 * 60 * 1000
- $('body').append($(''))
+ $('body').append($(''))
trackedHourOfCode = true
@requiresSubscription = not me.isPremium()
diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee
index f4e4633d2..ca263aaf1 100644
--- a/app/views/play/level/PlayLevelView.coffee
+++ b/app/views/play/level/PlayLevelView.coffee
@@ -352,6 +352,7 @@ module.exports = class PlayLevelView extends RootView
@realTimeMultiplayerContinueGame @options.realTimeMultiplayerSessionID
# TODO: Is it possible to create a Mongoose ObjectId for 'ls', instead of the string returned from get()?
application.tracker?.trackEvent 'Started Level', category:'Play Level', levelID: @levelID, ls: @session?.get('_id') unless @observing
+ $(window).trigger 'resize'
playAmbientSound: ->
return if @destroyed
diff --git a/app/views/play/level/tome/CastButtonView.coffee b/app/views/play/level/tome/CastButtonView.coffee
index 64b1324a8..6ad76861b 100644
--- a/app/views/play/level/tome/CastButtonView.coffee
+++ b/app/views/play/level/tome/CastButtonView.coffee
@@ -47,10 +47,7 @@ module.exports = class CastButtonView extends CocoView
afterRender: ->
super()
@castButton = $('.cast-button', @$el)
- @castOptions = $('.autocast-delays', @$el)
- #delay = me.get('autocastDelay') # No more autocast
- delay = 90019001
- @setAutocastDelay delay
+ spell.view?.createOnCodeChangeHandlers() for spellKey, spell of @spells
if @options.level.get('hidesSubmitUntilRun') or @options.level.get('hidesRealTimePlayback')
@$el.find('.submit-button').hide() # Hide Submit for the first few until they run it once.
if @options.session.get('state')?.complete and @options.level.get 'hidesRealTimePlayback'
@@ -150,17 +147,6 @@ module.exports = class CastButtonView extends CocoView
waitTime = moment().add(timeUntilResubmit, 'ms').fromNow()
submitAgainLabel.text waitTime
- setAutocastDelay: (delay) ->
- #console.log 'Set autocast delay to', delay
- return unless delay
- delay = 90019001 # No more autocast
- @autocastDelay = delay = parseInt delay
- me.set('autocastDelay', delay)
- me.patch()
- spell.view?.setAutocastDelay delay for spellKey, spell of @spells
- @castOptions.find('a').each ->
- $(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)
-
onJoinedRealTimeMultiplayerGame: (e) ->
@inRealTimeMultiplayerSession = true
diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee
index 55cc20947..5d13a8094 100644
--- a/app/views/play/level/tome/SpellView.coffee
+++ b/app/views/play/level/tome/SpellView.coffee
@@ -76,6 +76,7 @@ module.exports = class SpellView extends CocoView
@createACE()
@createACEShortcuts()
@fillACE()
+ @createOnCodeChangeHandlers()
@lockDefaultCode()
if @session.get('multiplayer')
@createFirepad()
@@ -613,11 +614,7 @@ module.exports = class SpellView extends CocoView
Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell
@updateLines()
- recompileIfNeeded: =>
- @recompile() if @recompileNeeded
-
recompile: (cast=true, realTime=false) ->
- @setRecompileNeeded false
hasChanged = @spell.source isnt @getSource()
if hasChanged
@spell.transpile @getSource()
@@ -640,17 +637,9 @@ module.exports = class SpellView extends CocoView
catch error
console.warn 'Error resizing ACE after an update:', error
- # Called from CastButtonView initially and whenever the delay is changed
- setAutocastDelay: (@autocastDelay) ->
- @createOnCodeChangeHandlers()
-
createOnCodeChangeHandlers: ->
@aceDoc.removeListener 'change', @onCodeChangeMetaHandler if @onCodeChangeMetaHandler
- autocastDelay = @autocastDelay ? 3000
- onSignificantChange = [
- _.debounce @setRecompileNeeded, autocastDelay - 100
- @currentAutocastHandler = _.debounce @recompileIfNeeded, autocastDelay
- ]
+ onSignificantChange = []
onAnyChange = [
_.debounce @updateAether, 500
_.debounce @notifyEditingEnded, 1000
@@ -671,10 +660,7 @@ module.exports = class SpellView extends CocoView
callback() for callback in onAnyChange # Then these
@aceDoc.on 'change', @onCodeChangeMetaHandler
- setRecompileNeeded: (@recompileNeeded) =>
-
- onCursorActivity: =>
- @currentAutocastHandler?()
+ onCursorActivity: => # Used to refresh autocast delay; doesn't do anything at the moment.
# Design for a simpler system?
# * Keep Aether linting, debounced, on any significant change
@@ -801,7 +787,7 @@ module.exports = class SpellView extends CocoView
@userCodeProblem.save()
null
- # Autocast:
+ # Autocast (preload the world in the background):
# Goes immediately if the code is a) changed and b) complete/valid and c) the cursor is at beginning or end of a line
# We originally thought it would:
# - Go after specified delay if a) and b) but not c)
@@ -815,12 +801,9 @@ module.exports = class SpellView extends CocoView
endOfLine = cursorPosition.column >= currentLine.length # just typed a semicolon or brace, for example
beginningOfLine = not currentLine.substr(0, cursorPosition.column).trim().length # uncommenting code, for example
incompleteThis = /^(s|se|sel|self|t|th|thi|this)$/.test currentLine.trim()
- # console.log "finished=#{valid and (endOfLine or beginningOfLine) and not incompleteThis}", valid, endOfLine, beginningOfLine, incompleteThis, cursorPosition, currentLine.length, aether, new Date() - 0, currentLine
+ #console.log "finished=#{valid and (endOfLine or beginningOfLine) and not incompleteThis}", valid, endOfLine, beginningOfLine, incompleteThis, cursorPosition, currentLine.length, aether, new Date() - 0, currentLine
if valid and (endOfLine or beginningOfLine) and not incompleteThis
- if @autocastDelay > 60000
- @preload()
- else
- @recompile()
+ @preload()
singleLineCommentRegex: ->
if @_singleLineCommentRegex
@@ -836,7 +819,10 @@ module.exports = class SpellView extends CocoView
preload: ->
# Send this code over to the God for preloading, but don't change the cast state.
- return if @spell.source.indexOf 'while' # If they're working with while-loops, it's more likely to be an incomplete infinite loop, so don't preload.
+ #console.log 'preload?', @spell.source.indexOf('while'), @spell.source.length, @spellThang?.castAether?.metrics?.statementsExecuted
+ return if @spell.source.indexOf('while') isnt -1 # If they're working with while-loops, it's more likely to be an incomplete infinite loop, so don't preload.
+ return if @spell.source.length > 500 # Only preload on really short methods
+ return if @spellThang?.castAether?.metrics?.statementsExecuted > 2000 # Don't preload if they are running significant amounts of user code
oldSource = @spell.source
oldSpellThangAethers = {}
for thangID, spellThang of @spell.thangs
diff --git a/app/views/play/level/tome/TomeView.coffee b/app/views/play/level/tome/TomeView.coffee
index cd08ce075..eaab9776d 100644
--- a/app/views/play/level/tome/TomeView.coffee
+++ b/app/views/play/level/tome/TomeView.coffee
@@ -1,7 +1,7 @@
# There's one TomeView per Level. It has:
# - a CastButtonView, which has
# - a cast button
-# - an autocast settings options button
+# - a submit/done button
# - for each spell (programmableMethod):
# - a Spell, which has
# - a list of Thangs that share that Spell, with one aether per Thang per Spell
diff --git a/app/views/play/menu/GameMenuModal.coffee b/app/views/play/menu/GameMenuModal.coffee
index 9dad94e47..7e5c3aac2 100644
--- a/app/views/play/menu/GameMenuModal.coffee
+++ b/app/views/play/menu/GameMenuModal.coffee
@@ -43,6 +43,7 @@ module.exports = class GameMenuModal extends ModalView
'guide': 'list'
'save-load': 'floppy-disk'
'multiplayer': 'globe'
+ context.showsChooseHero = @options.levelID not in ['zero-sum']
context
afterRender: ->
diff --git a/app/views/play/menu/OptionsView.coffee b/app/views/play/menu/OptionsView.coffee
index 8b76b4a1e..205057065 100644
--- a/app/views/play/menu/OptionsView.coffee
+++ b/app/views/play/menu/OptionsView.coffee
@@ -20,7 +20,6 @@ module.exports = class OptionsView extends CocoView
events:
'change #option-music': 'updateMusic'
- 'change #option-autorun-delay': 'updateAutorun'
'change #option-key-bindings': 'updateInvisibles'
'change #option-key-bindings': 'updateKeyBindings'
'change #option-indent-guides': 'updateIndentGuides'
@@ -44,7 +43,6 @@ module.exports = class OptionsView extends CocoView
@aceConfig = _.defaults @aceConfig, @defaultConfig
c.aceConfig = @aceConfig
c.music = me.get('music', true)
- c.autorunDelay = me.get('autocastDelay') ? 5000
c
afterRender: ->
@@ -80,9 +78,6 @@ module.exports = class OptionsView extends CocoView
updateMusic: ->
me.set 'music', @$el.find('#option-music').prop('checked')
- updateAutorun: ->
- me.set 'autocastDelay', parseInt(@$el.find('#option-autorun-delay').val())
-
updateInvisibles: ->
@aceConfig.invisibles = @$el.find('#option-invisibles').prop('checked')
diff --git a/bower.json b/bower.json
index ea8592d3f..484277bcb 100644
--- a/bower.json
+++ b/bower.json
@@ -47,7 +47,8 @@
"modernizr": "~2.8.3",
"backfire": "~0.3.0",
"fastclick": "~1.0.3",
- "three.js": "*"
+ "three.js": "*",
+ "lscache": "~1.0.5"
},
"overrides": {
"backbone": {