mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-11-24 16:17:57 -05:00
Merge branch 'master' into production
This commit is contained in:
commit
9346b93cfd
20 changed files with 262 additions and 67 deletions
|
@ -4,9 +4,16 @@ var Global = self;
|
|||
importScripts("/javascripts/lodash.js", "/javascripts/aether.js");
|
||||
//console.log("Aether Tome worker has finished importing scripts.");
|
||||
var aethers = {};
|
||||
var languagesImported = {};
|
||||
|
||||
var createAether = function (spellKey, options)
|
||||
{
|
||||
var ensureLanguageImported = function(language) {
|
||||
if (languagesImported[language]) return;
|
||||
importScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||
languagesImported[language] = true;
|
||||
};
|
||||
|
||||
var createAether = function (spellKey, options) {
|
||||
ensureLanguageImported(options.language);
|
||||
aethers[spellKey] = new Aether(options);
|
||||
return JSON.stringify({
|
||||
"message": "Created aether for " + spellKey,
|
||||
|
@ -15,7 +22,6 @@ var createAether = function (spellKey, options)
|
|||
};
|
||||
|
||||
var hasChangedSignificantly = function(spellKey, a,b,careAboutLineNumbers,careAboutLint) {
|
||||
|
||||
var hasChanged = aethers[spellKey].hasChangedSignificantly(a,b,careAboutLineNumbers,careAboutLint);
|
||||
var functionName = "hasChangedSignificantly";
|
||||
var returnObject = {
|
||||
|
@ -26,8 +32,8 @@ var hasChangedSignificantly = function(spellKey, a,b,careAboutLineNumbers,careAb
|
|||
return JSON.stringify(returnObject);
|
||||
};
|
||||
|
||||
var updateLanguageAether = function(newLanguage)
|
||||
{
|
||||
var updateLanguageAether = function(newLanguage) {
|
||||
ensureLanguageImported(newLanguage);
|
||||
for (var spellKey in aethers)
|
||||
{
|
||||
if (aethers.hasOwnProperty(spellKey))
|
||||
|
@ -38,8 +44,7 @@ var updateLanguageAether = function(newLanguage)
|
|||
}
|
||||
};
|
||||
|
||||
var lint = function(spellKey, source)
|
||||
{
|
||||
var lint = function(spellKey, source) {
|
||||
var currentAether = aethers[spellKey];
|
||||
var lintMessages = currentAether.lint(source);
|
||||
var functionName = "lint";
|
||||
|
@ -50,8 +55,7 @@ var lint = function(spellKey, source)
|
|||
return JSON.stringify(returnObject);
|
||||
};
|
||||
|
||||
var transpile = function(spellKey, source)
|
||||
{
|
||||
var transpile = function(spellKey, source) {
|
||||
var currentAether = aethers[spellKey];
|
||||
currentAether.transpile(source);
|
||||
var functionName = "transpile";
|
||||
|
@ -62,6 +66,7 @@ var transpile = function(spellKey, source)
|
|||
};
|
||||
return JSON.stringify(returnObject);
|
||||
};
|
||||
|
||||
self.addEventListener('message', function(e) {
|
||||
var data = JSON.parse(e.data);
|
||||
if (data.function == "createAether")
|
||||
|
@ -70,7 +75,7 @@ self.addEventListener('message', function(e) {
|
|||
}
|
||||
else if (data.function == "updateLanguageAether")
|
||||
{
|
||||
updateLanguageAether(data.newLanguage)
|
||||
updateLanguageAether(data.newLanguage);
|
||||
}
|
||||
else if (data.function == "hasChangedSignificantly")
|
||||
{
|
||||
|
|
|
@ -64,6 +64,24 @@ console.error = console.warn = console.info = console.debug = console.log;
|
|||
self.console = console;
|
||||
|
||||
self.importScripts('/javascripts/lodash.js', '/javascripts/world.js', '/javascripts/aether.js');
|
||||
var myImportScripts = importScripts;
|
||||
|
||||
var languagesImported = {};
|
||||
var ensureLanguageImported = function(language) {
|
||||
if (languagesImported[language]) return;
|
||||
if (language === 'javascript') return; // Only has JSHint, but we don't need to lint here.
|
||||
myImportScripts("/javascripts/app/vendor/aether-" + language + ".js");
|
||||
languagesImported[language] = true;
|
||||
};
|
||||
|
||||
var ensureLanguagesImportedFromUserCodeMap = function (userCodeMap) {
|
||||
for (var thangID in userCodeMap)
|
||||
for (var spellName in userCodeMap[thangID]) {
|
||||
var language = userCodeMap[thangID][spellName].originalOptions.language;
|
||||
ensureLanguageImported(language);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var restricted = ["XMLHttpRequest", "Worker"];
|
||||
if (!self.navigator || !(self.navigator.userAgent.indexOf('MSIE') > 0) &&
|
||||
|
@ -283,6 +301,7 @@ self.setupDebugWorldToRunUntilFrame = function (args) {
|
|||
self.debugt0 = new Date();
|
||||
self.logsLogged = 0;
|
||||
|
||||
ensureLanguagesImportedFromUserCodeMap(args.userCodeMap);
|
||||
var stringifiedUserCodeMap = JSON.stringify(args.userCodeMap);
|
||||
var userCodeMapHasChanged = ! _.isEqual(self.currentUserCodeMapCopy, stringifiedUserCodeMap);
|
||||
self.currentUserCodeMapCopy = stringifiedUserCodeMap;
|
||||
|
@ -347,6 +366,7 @@ self.runWorld = function runWorld(args) {
|
|||
self.logsLogged = 0;
|
||||
|
||||
try {
|
||||
ensureLanguagesImportedFromUserCodeMap(args.userCodeMap);
|
||||
self.world = new World(args.userCodeMap);
|
||||
self.world.levelSessionIDs = args.levelSessionIDs;
|
||||
self.world.submissionCount = args.submissionCount;
|
||||
|
|
|
@ -77,7 +77,13 @@ module.exports = ModuleLoader = class ModuleLoader extends CocoClass
|
|||
|
||||
# just a bit of cleanup to get the script objects out of the body element
|
||||
$(e.result).remove()
|
||||
|
||||
|
||||
# get treema set up only when the library loads, if it loads
|
||||
if e.item.id is 'vendor/treema'
|
||||
console.log 'setting up treema-ext'
|
||||
treemaExt = require 'core/treema-ext'
|
||||
treemaExt.setup()
|
||||
|
||||
# a module and its dependencies have loaded!
|
||||
if @queue.progress is 1
|
||||
$('#module-loading-list').modal('hide')
|
||||
|
@ -86,11 +92,7 @@ module.exports = ModuleLoader = class ModuleLoader extends CocoClass
|
|||
console.debug 'loaded', @recentPaths.length, 'files,', parseInt(@recentLoadedBytes/1024), 'KB'
|
||||
@trigger 'load-complete'
|
||||
|
||||
# get treema set up only when the library loads, if it loads
|
||||
if e.item.id is 'vendor/treema'
|
||||
console.log 'setting up treema-ext'
|
||||
treemaExt = require 'core/treema-ext'
|
||||
treemaExt.setup()
|
||||
@trigger 'loaded', e.item
|
||||
|
||||
parseDependencies: (raw) ->
|
||||
bits = raw.match(/(require\(['"](.+?)['"])|(register\(['"].+?['"])/g) or []
|
||||
|
|
|
@ -89,6 +89,16 @@ module.exports = class LevelLoader extends CocoClass
|
|||
|
||||
loadDependenciesForSession: (session) ->
|
||||
if session is @session
|
||||
codeLanguage = session.get('codeLanguage') or me.get('aceConfig')?.language or 'python'
|
||||
modulePath = "vendor/aether-#{codeLanguage}"
|
||||
loading = application.moduleLoader.load(modulePath)
|
||||
if loading
|
||||
@languageModuleResource = @supermodel.addSomethingResource 'language_module'
|
||||
@listenTo application.moduleLoader, 'loaded', (e) ->
|
||||
if e.id is modulePath
|
||||
@languageModuleResource.markLoaded()
|
||||
@stopListening application.moduleLoader
|
||||
|
||||
# hero-ladder games require the correct session team in level:loaded
|
||||
team = @team ? @session.get('team')
|
||||
Backbone.Mediator.publish 'level:loaded', level: @level, team: team
|
||||
|
|
|
@ -116,11 +116,13 @@ class CocoModel extends Backbone.Model
|
|||
|
||||
save: (attrs, options) ->
|
||||
options ?= {}
|
||||
originalOptions = _.cloneDeep(options)
|
||||
options.headers ?= {}
|
||||
options.headers['X-Current-Path'] = document.location?.pathname ? 'unknown'
|
||||
success = options.success
|
||||
error = options.error
|
||||
options.success = (model, res) =>
|
||||
@retries = 0
|
||||
@trigger 'save:success', @
|
||||
success(@, res) if success
|
||||
@markToRevert() if @_revertAttributes
|
||||
|
@ -128,6 +130,17 @@ class CocoModel extends Backbone.Model
|
|||
CocoModel.pollAchievements()
|
||||
options.success = options.error = null # So the callbacks can be garbage-collected.
|
||||
options.error = (model, res) =>
|
||||
if res.status is 0
|
||||
@retries ?= 0
|
||||
@retries += 1
|
||||
if @retries > 20
|
||||
msg = 'Your computer or our servers appear to be offline. Please try refreshing.'
|
||||
noty text: msg, layout: 'center', type: 'error', killer: true
|
||||
return
|
||||
else
|
||||
msg = $.i18n.t 'loading_error.connection_failure', defaultValue: 'Connection failed.'
|
||||
noty text: msg, layout: 'center', type: 'error', killer: true, timeout: 3000
|
||||
return _.delay((f = => @save(attrs, originalOptions)), 3000)
|
||||
error(@, res) if error
|
||||
return unless @notyErrors
|
||||
errorMessage = "Error saving #{@get('name') ? @type()}"
|
||||
|
|
|
@ -104,6 +104,7 @@ module.exports = class User extends CocoModel
|
|||
|
||||
getBranchingGroup: ->
|
||||
return @branchingGroup if @branchingGroup
|
||||
return 'no-practice' # A/B test paused for school testing
|
||||
group = me.get('testGroupNumber') % 4
|
||||
@branchingGroup = switch group
|
||||
when 0 then 'no-practice'
|
||||
|
@ -124,15 +125,6 @@ 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
|
||||
]
|
||||
|
|
|
@ -146,6 +146,8 @@ _.extend ThangTypeSchema.properties,
|
|||
maleRangerThumb: { type: 'string', format: 'image-file', title: 'Thumb (Male Ranger)' }
|
||||
femaleRanger: { type: 'string', format: 'image-file', title: 'Glove (Female Ranger)' }
|
||||
femaleRangeThumbr: { type: 'string', format: 'image-file', title: 'Thumb (Female Ranger)' }
|
||||
maleBack: { type: 'string', format: 'image-file', title: ' Male Back' }
|
||||
femaleBack: { type: 'string', format: 'image-file', title: ' Female Back' }
|
||||
colorGroups: c.object
|
||||
title: 'Color Groups'
|
||||
additionalProperties:
|
||||
|
|
|
@ -489,10 +489,16 @@ $itemSlotGridHeight: 51px
|
|||
left: 65px
|
||||
bottom: 31px
|
||||
|
||||
&.Ranger
|
||||
left: -7px
|
||||
|
||||
&.female
|
||||
left: 80px
|
||||
bottom: 31px
|
||||
|
||||
&.Ranger
|
||||
left: -7px
|
||||
|
||||
#hero-image-head
|
||||
z-index: 16
|
||||
#hero-image-hair
|
||||
|
@ -500,12 +506,12 @@ $itemSlotGridHeight: 51px
|
|||
#hero-image-thumb
|
||||
z-index: 16
|
||||
|
||||
&.female
|
||||
&.female:not(.Ranger)
|
||||
@include rotate(-15deg)
|
||||
left: 66px
|
||||
bottom: 54px
|
||||
|
||||
&.male
|
||||
&.male:not(.Ranger)
|
||||
@include rotate(-15deg)
|
||||
left: 53px
|
||||
bottom: 54px
|
||||
|
@ -525,7 +531,7 @@ $itemSlotGridHeight: 51px
|
|||
&.feet
|
||||
z-index: 13
|
||||
|
||||
&.right-hand
|
||||
&.right-hand:not(.Ranger)
|
||||
@include rotate(-15deg)
|
||||
&.female
|
||||
left: 66px
|
||||
|
@ -542,7 +548,7 @@ $itemSlotGridHeight: 51px
|
|||
&.torso
|
||||
z-index: 14
|
||||
|
||||
&.gloves
|
||||
&.gloves:not(.Ranger)
|
||||
z-index: 15
|
||||
|
||||
&.female
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "app/styles/mixins"
|
||||
|
||||
#item-details-view
|
||||
|
||||
.nano-content
|
||||
|
@ -42,6 +44,9 @@
|
|||
|
||||
.item-shadow
|
||||
top: 25px
|
||||
left: 5px
|
||||
@include filter(contrast(0%) brightness(0%))
|
||||
opacity: 0.2
|
||||
|
||||
img.hr
|
||||
width: 80%
|
||||
|
|
|
@ -159,6 +159,11 @@ $gameControlMargin: 30px
|
|||
margin-bottom: -2 * $levelDotShadowHeight / 3
|
||||
|
||||
.level:hover
|
||||
// TODO: This rotate stops Firefox from flickering, but also disables the scaleY(0.75)
|
||||
// TODO: The dot looks like it's jumping.
|
||||
// TODO: -moz-transform: scaleY(0.75) didn't do anything
|
||||
// TODO: Does not break Chrome's oval.
|
||||
-moz-transform: rotate(0)
|
||||
margin-bottom: -$levelDotHeight / 3 + $levelDotHoverZ
|
||||
@include box-shadow(0px 0px 35px skyblue)
|
||||
|
||||
|
|
|
@ -31,15 +31,14 @@
|
|||
if item.silhouetted && !item.owned
|
||||
span.glyphicon.glyphicon-lock.bolder
|
||||
span.glyphicon.glyphicon-lock
|
||||
img.item-silhouette(src=item.getPortraitURL(), draggable="false")
|
||||
img.item-silhouette(draggable="false")
|
||||
if item.level
|
||||
.required-level
|
||||
div(data-i18n="general.player_level")
|
||||
div= item.level
|
||||
else
|
||||
strong.big-font= item.name
|
||||
img.item-img(src=item.getPortraitURL(), draggable="false")
|
||||
//img.item-shadow(src=item.getPortraitURL(), draggable="false") // Not performant, takes too much memory with filter
|
||||
img.item-img(draggable="false")
|
||||
|
||||
if item.owned
|
||||
span.big-font.owned(data-i18n="play.owned")
|
||||
|
|
|
@ -10,32 +10,30 @@
|
|||
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;
|
||||
//- 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
|
||||
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'
|
||||
|
|
|
@ -49,6 +49,7 @@ module.exports = class CocoView extends Backbone.View
|
|||
@listenTo(@supermodel, 'loaded-all', @onLoaded)
|
||||
@listenTo(@supermodel, 'update-progress', @updateProgress)
|
||||
@listenTo(@supermodel, 'failed', @onResourceLoadFailed)
|
||||
@warnConnectionError = _.throttle(@warnConnectionError, 3000)
|
||||
|
||||
super options
|
||||
|
||||
|
@ -149,6 +150,17 @@ module.exports = class CocoView extends Backbone.View
|
|||
# Error handling for loading
|
||||
onResourceLoadFailed: (e) ->
|
||||
r = e.resource
|
||||
if r.jqxhr?.status is 0
|
||||
r.retries ?= 0
|
||||
r.retries += 1
|
||||
if r.retries > 20
|
||||
msg = 'Your computer or our servers appear to be offline. Please try refreshing.'
|
||||
noty text: msg, layout: 'center', type: 'error', killer: true
|
||||
return
|
||||
else
|
||||
@warnConnectionError()
|
||||
return _.delay (=> r.load()), 3000
|
||||
|
||||
@$el.find('.loading-container .errors').append(loadingErrorTemplate({
|
||||
status: r.jqxhr?.status
|
||||
name: r.name
|
||||
|
@ -157,6 +169,10 @@ module.exports = class CocoView extends Backbone.View
|
|||
})).i18n()
|
||||
@$el.find('.progress').hide()
|
||||
|
||||
warnConnectionError: ->
|
||||
msg = $.i18n.t 'loading_error.connection_failure', defaultValue: 'Connection failed.'
|
||||
noty text: msg, layout: 'center', type: 'error', killer: true, timeout: 3000
|
||||
|
||||
onRetryResource: (e) ->
|
||||
res = @supermodel.getResource($(e.target).data('resource-index'))
|
||||
# different views may respond to this call, and not all have the resource to reload
|
||||
|
|
|
@ -69,9 +69,7 @@ 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')
|
||||
# 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()
|
||||
window.tracker?.trackEvent 'Loaded World Map', category: 'World Map', ['Google Analytics']
|
||||
|
||||
# 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')))
|
||||
|
@ -137,8 +135,6 @@ 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: ->
|
||||
|
|
|
@ -293,7 +293,7 @@ module.exports = class PlayHeroesModal extends ModalView
|
|||
if @session.get('codeLanguage') isnt @codeLanguage
|
||||
@session.set('codeLanguage', @codeLanguage)
|
||||
changed = true
|
||||
Backbone.Mediator.publish 'tome:change-language', language: @codeLanguage, reload: true
|
||||
#Backbone.Mediator.publish 'tome:change-language', language: @codeLanguage, reload: true # We'll reload the PlayLevelView instead.
|
||||
|
||||
@session.patch() if changed
|
||||
|
||||
|
|
|
@ -51,8 +51,10 @@ module.exports = class PlayItemsModal extends ModalView
|
|||
'click .buy-gems-prompt-button': 'onBuyGemsPromptButtonClicked'
|
||||
'click #close-modal': 'hide'
|
||||
'click': 'onClickedSomewhere'
|
||||
'update .tab-pane .nano': 'onScrollItemPane'
|
||||
|
||||
constructor: (options) ->
|
||||
@onScrollItemPane = _.throttle(_.bind(@onScrollItemPane, @), 200)
|
||||
super options
|
||||
@items = new Backbone.Collection()
|
||||
@itemCategoryCollections = {}
|
||||
|
@ -143,7 +145,21 @@ module.exports = class PlayItemsModal extends ModalView
|
|||
|
||||
onTabClicked: (e) ->
|
||||
@playSound 'game-menu-tab-switch'
|
||||
$($(e.target).attr('href')).find('.nano').nanoScroller({alwaysVisible: true})
|
||||
nano = $($(e.target).attr('href')).find('.nano')
|
||||
nano.nanoScroller({alwaysVisible: true})
|
||||
@paneNanoContent = nano.find('.nano-content')
|
||||
@onScrollItemPane()
|
||||
|
||||
onScrollItemPane: ->
|
||||
# dynamically load visible items when the user scrolls enough to see them
|
||||
items = @paneNanoContent.find('.item:not(.loaded)')
|
||||
threshold = @paneNanoContent.height() + 100
|
||||
for itemEl in items
|
||||
itemEl = $(itemEl)
|
||||
if itemEl.position().top < threshold
|
||||
$(itemEl).addClass('loaded')
|
||||
item = @idToItem[itemEl.data('item-id')]
|
||||
itemEl.find('.item-silhouette, .item-img').attr('src', item.getPortraitURL())
|
||||
|
||||
onUnlockButtonClicked: (e) ->
|
||||
e.stopPropagation()
|
||||
|
|
13
bower.json
13
bower.json
|
@ -32,7 +32,7 @@
|
|||
"firepad": "~0.1.2",
|
||||
"marked": "~0.3.0",
|
||||
"moment": "~2.5.0",
|
||||
"aether": "~0.2.39",
|
||||
"aether": "~0.3.0",
|
||||
"underscore.string": "~2.3.3",
|
||||
"firebase": "~1.0.2",
|
||||
"d3": "~3.4.4",
|
||||
|
@ -96,6 +96,17 @@
|
|||
},
|
||||
"modernizr": {
|
||||
"main": "modernizr.js"
|
||||
},
|
||||
"aether": {
|
||||
"main": [
|
||||
"build/aether.js",
|
||||
"build/clojure.js",
|
||||
"build/coffeescript.js",
|
||||
"build/io.js",
|
||||
"build/javascript.js",
|
||||
"build/lua.js",
|
||||
"build/python.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -88,6 +88,12 @@ exports.config =
|
|||
'javascripts/box2d.js': regJoin('^vendor/scripts/Box2dWeb-2.1.a.3')
|
||||
'javascripts/lodash.js': regJoin('^bower_components/lodash/dist/lodash.js')
|
||||
'javascripts/aether.js': regJoin('^bower_components/aether/build/aether.js')
|
||||
'javascripts/app/vendor/aether-clojure.js': 'bower_components/aether/build/clojure.js'
|
||||
'javascripts/app/vendor/aether-coffeescript.js': 'bower_components/aether/build/coffeescript.js'
|
||||
'javascripts/app/vendor/aether-io.js': 'bower_components/aether/build/io.js'
|
||||
'javascripts/app/vendor/aether-javascript.js': 'bower_components/aether/build/javascript.js'
|
||||
'javascripts/app/vendor/aether-lua.js': 'bower_components/aether/build/lua.js'
|
||||
'javascripts/app/vendor/aether-python.js': 'bower_components/aether/build/python.js'
|
||||
|
||||
# Any vendor libraries we don't want the client to load immediately
|
||||
'javascripts/app/vendor/d3.js': regJoin('^bower_components/d3')
|
||||
|
|
|
@ -66,7 +66,7 @@
|
|||
"redis": "",
|
||||
"webworker-threads": "~0.4.11",
|
||||
"node-gyp": "~0.13.0",
|
||||
"aether": "~0.2.39",
|
||||
"aether": "~0.3.0",
|
||||
"JASON": "~0.1.3",
|
||||
"JQDeferred": "~2.1.0",
|
||||
"jsondiffpatch": "0.1.17",
|
||||
|
|
93
scripts/mongodb/queries/playtime-breakdown.coffee
Normal file
93
scripts/mongodb/queries/playtime-breakdown.coffee
Normal file
|
@ -0,0 +1,93 @@
|
|||
# Life's too short to write these things in JS, so install cofmon:
|
||||
# npm install -g cofmon
|
||||
# Then you can paste CoffeeScript into it.
|
||||
|
||||
nDays = 1
|
||||
dayOffset = 0.3
|
||||
now = new Date()
|
||||
startDate = new Date(now - 86400 * 1000 * (nDays + dayOffset))
|
||||
endDate = new Date(now - 86400 * 1000 * dayOffset)
|
||||
|
||||
users = db.users.find({dateCreated: {$gt: startDate, $lt: endDate}}, {_id: 1, name: 1, testGroupNumber: 1}).toArray()
|
||||
goodUsers = []
|
||||
for user in users
|
||||
totalPlaytime = 0
|
||||
sessions = db.level.sessions.find({creator: '' + user._id}, {playtime: 1, levelID: 1}).toArray()
|
||||
firstSessions = []
|
||||
for session in sessions when session.playtime
|
||||
totalPlaytime += session.playtime
|
||||
if totalPlaytime > 60 * 60
|
||||
break
|
||||
firstSessions.push session
|
||||
if totalPlaytime < 60 * 60
|
||||
continue
|
||||
goodUsers.push {user: user, playtime: totalPlaytime, sessions: firstSessions}
|
||||
|
||||
levelUserCounts = {}
|
||||
for user in goodUsers
|
||||
for session in user.sessions
|
||||
levelUserCounts[session.levelID] ?= 0
|
||||
levelUserCounts[session.levelID]++
|
||||
|
||||
print "Found #{goodUsers.length} users who played more than an hour out of #{users.length}."
|
||||
print "Levels by number of users completing:"
|
||||
levelUserCounts
|
||||
|
||||
|
||||
"""
|
||||
Found 194 users who played more than an hour out of 93952.
|
||||
rs0:PRIMARY> levelUserCounts;
|
||||
{
|
||||
"dungeons-of-kithgard" : 190,
|
||||
"gems-in-the-deep" : 184,
|
||||
"shadow-guard" : 186,
|
||||
"forgetful-gemsmith" : 189,
|
||||
"kounter-kithwise" : 80,
|
||||
"true-names" : 186,
|
||||
"favorable-odds" : 76,
|
||||
"the-raised-sword" : 181,
|
||||
"haunted-kithmaze" : 181,
|
||||
"descending-further" : 70,
|
||||
"the-second-kithmaze" : 171,
|
||||
"dread-door" : 172,
|
||||
"known-enemy" : 170,
|
||||
"master-of-names" : 160,
|
||||
"lowly-kithmen" : 138,
|
||||
"closing-the-distance" : 137,
|
||||
"tactical-strike" : 48,
|
||||
"the-final-kithmaze" : 108,
|
||||
"the-gauntlet" : 43,
|
||||
"kithgard-gates" : 96,
|
||||
"defense-of-plainswood" : 88,
|
||||
"winding-trail" : 75,
|
||||
"endangered-burl" : 51,
|
||||
"village-guard" : 40,
|
||||
"thornbush-farm" : 33,
|
||||
"back-to-back" : 27,
|
||||
"ogre-encampment" : 22,
|
||||
"woodland-cleaver" : 18,
|
||||
"shield-rush" : 10,
|
||||
"peasant-protection" : 8,
|
||||
"munchkin-swarm" : 10,
|
||||
"munchkin-harvest" : 4,
|
||||
"swift-dagger" : 1,
|
||||
"shrapnel" : 1,
|
||||
"arcane-ally" : 1,
|
||||
"touch-of-death" : 1
|
||||
"bonemender" : 1,
|
||||
"coinucopia" : 6,
|
||||
"copper-meadows" : 3,
|
||||
"drop-the-flag" : 3,
|
||||
"deadly-pursuit" : 2,
|
||||
"rich-forager" : 1,
|
||||
"multiplayer-treasure-grove" : 1,
|
||||
|
||||
"rescue-mission" : 2,
|
||||
"dungeon-arena-tutorial" : 3,
|
||||
"dungeon-arena" : 2,
|
||||
"undefined" : 2,
|
||||
"grab-the-mushroom" : 2,
|
||||
"gold-rush" : 1,
|
||||
"criss-cross" : 1,
|
||||
}
|
||||
"""
|
Loading…
Reference in a new issue