diff --git a/app/lib/scripts/ScriptManager.coffee b/app/lib/scripts/ScriptManager.coffee index 9892e0201..796551f58 100644 --- a/app/lib/scripts/ScriptManager.coffee +++ b/app/lib/scripts/ScriptManager.coffee @@ -88,7 +88,7 @@ module.exports = ScriptManager = class ScriptManager extends CocoClass scriptStates: scriptStates timeSinceLastScriptEnded: (if @lastScriptEnded then now - @lastScriptEnded else 0) / 1000 - Backbone.Mediator.publish 'script:tick', stateEvent # Is this even needed? + Backbone.Mediator.publish 'script:tick', stateEvent # Used to trigger level scripts. loadFromSession: -> # load the queue with note groups to skip through diff --git a/app/lib/surface/CoordinateGrid.coffee b/app/lib/surface/CoordinateGrid.coffee index 113731141..e5499fdea 100644 --- a/app/lib/surface/CoordinateGrid.coffee +++ b/app/lib/surface/CoordinateGrid.coffee @@ -24,8 +24,8 @@ module.exports = class CoordinateGrid extends CocoClass toString: -> '' build: (worldSize) -> - worldWidth = worldSize[0] ? 80 - worldHeight = worldSize[1] ? 68 + worldWidth = worldSize[0] or 80 + worldHeight = worldSize[1] or 68 @gridContainer = new createjs.Container() @gridShape = new createjs.Shape() @gridContainer.addChild @gridShape diff --git a/app/lib/surface/SpriteBoss.coffee b/app/lib/surface/SpriteBoss.coffee index 1a912b555..bedb45d6c 100644 --- a/app/lib/surface/SpriteBoss.coffee +++ b/app/lib/surface/SpriteBoss.coffee @@ -224,7 +224,7 @@ module.exports = class SpriteBoss extends CocoClass for slot, itemID of thang.inventoryIDs item = @world.getThangByID itemID unless item.equipped - #console.log thang.id, 'equipping', item, 'in', thang.slot, 'Surface-side' + console.log thang.id, 'equipping', item, 'in', thang.slot, 'Surface-side, but it cannot equip?' unless item.equip item.equip() itemsJustEquipped.push item return itemsJustEquipped diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 73fef4d89..ea8b1ac0c 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -486,7 +486,6 @@ module.exports = Surface = class Surface extends CocoClass onMouseUp: (e) => return if @disabled - console.log 'yo on mouse up', e onBackground = not @stage.hitTest e.stageX, e.stageY Backbone.Mediator.publish 'surface:stage-mouse-up', onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e @@ -497,8 +496,8 @@ module.exports = Surface = class Surface extends CocoClass event = deltaX: e.deltaX deltaY: e.deltaY - screenPos: @mouseScreenPos canvas: @canvas + event.screenPos = @mouseScreenPos if @mouseScreenPos Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled hookUpChooseControls: -> diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 6d7ac8d8f..14c559e90 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -126,6 +126,8 @@ level_difficulty: "Difficulty: " play_as: "Play As" spectate: "Spectate" + players: "players" + hours_played: "hours played" contact: contact_us: "Contact CodeCombat" @@ -511,6 +513,7 @@ toggle_grid: "Toggle grid overlay." toggle_pathfinding: "Toggle pathfinding overlay." beautify: "Beautify your code by standardizing its formatting." + maximize_editor: "Maximize/minimize code editor." move_wizard: "Move your Wizard around the level." admin: @@ -948,6 +951,7 @@ wizard: "Wizard" achievement: "Achievement" clas: "CLAs" + play_counts: "Play Counts" delta: added: "Added" diff --git a/app/locale/sv.coffee b/app/locale/sv.coffee index 946945816..9e499f9c5 100644 --- a/app/locale/sv.coffee +++ b/app/locale/sv.coffee @@ -3,11 +3,11 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr loading: "Laddar..." saving: "Sparar..." sending: "Skickar..." -# send: "Send" + send: "Skicka" cancel: "Avbryt" save: "Spara" -# publish: "Publish" -# create: "Create" + publish: "Publisera" + create: "Skapa" delay_1_sec: "1 sekund" delay_3_sec: "3 sekunder" delay_5_sec: "5 sekunder" @@ -19,21 +19,21 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr # unwatch: "Unwatch" # submit_patch: "Submit Patch" -# units: -# second: "second" -# seconds: "seconds" -# minute: "minute" -# minutes: "minutes" -# hour: "hour" -# hours: "hours" -# day: "day" -# days: "days" -# week: "week" -# weeks: "weeks" -# month: "month" -# months: "months" -# year: "year" -# years: "years" + units: + second: "sekund" + seconds: "sekunder" + minute: "minut" + minutes: "minuter" + hour: "timme" + hours: "timmar" + day: "dag" + days: "dagar" + week: "vecka" + weeks: "veckor" + month: "månad" + months: "månader" + year: "år" + years: "år" modal: close: "Stäng" @@ -44,14 +44,14 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr nav: play: "Spela" -# community: "Community" + community: "Community" editor: "Nivåredigerare" blog: "Blogg" forum: "Forum" -# account: "Account" -# profile: "Profile" -# stats: "Stats" -# code: "Code" + account: "Konto" + profile: "Profil" + stats: "Stats" + code: "Kod" admin: "Admin" home: "Hem" contribute: "Bidra" @@ -72,9 +72,9 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr login: sign_up: "Skapa konto" log_in: "Logga in" -# logging_in: "Logging In" + logging_in: "Loggar In" log_out: "Logga ut" - recover: "glömt lösenord" + recover: "Glömt lösenord" recover: recover_account_title: "Återskapa ditt konto" @@ -89,7 +89,7 @@ module.exports = nativeDescription: "Svenska", englishDescription: "Swedish", tr creating: "Skapar konto..." sign_up: "Skapa konto" log_in: "logga in med lösenord" -# social_signup: "Or, you can sign up through Facebook or G+:" + social_signup: "Eller så kan du logga in genom facebook eller g+:" # required: "You need to log in before you can go that way." home: diff --git a/app/schemas/models/level.coffee b/app/schemas/models/level.coffee index 7a8fd713c..90bcde6c0 100644 --- a/app/schemas/models/level.coffee +++ b/app/schemas/models/level.coffee @@ -109,10 +109,6 @@ NoteGroupSchema = c.object {title: 'Note Group', description: 'A group of notes lock: {title: 'Lock', description: 'Whether the interface should be locked so that the player\'s focus is on the script, or specific areas to lock.', type: ['boolean', 'array'], items: {type: 'string', enum: ['surface', 'editor', 'palette', 'hud', 'playback', 'playback-hover', 'level']}} letterbox: {type: 'boolean', title: 'Letterbox', description: 'Turn letterbox mode on or off. Disables surface and playback controls.'} - goals: c.object {title: 'Goals (Old)', description: 'Deprecated. Goals added here have no effect. Add goals in the level settings instead.'}, - add: c.array {title: 'Add', description: 'Deprecated. Goals added here have no effect. Add goals in the level settings instead.'}, {} - remove: c.array {title: 'Remove', description: 'Deprecated. Goals removed here have no effect. Adjust goals in the level settings instead.'}, {} - playback: c.object {title: 'Playback', description: 'Control the playback of the level.'}, playing: {type: 'boolean', title: 'Set Playing', description: 'Set whether playback is playing or paused.'} scrub: c.object {title: 'Scrub', description: 'Scrub the level playback time to a certain point.', default: {offset: 2, duration: 1000, toRatio: 0.5}}, diff --git a/app/schemas/subscriptions/editor.coffee b/app/schemas/subscriptions/editor.coffee index 04ee81e13..196723623 100644 --- a/app/schemas/subscriptions/editor.coffee +++ b/app/schemas/subscriptions/editor.coffee @@ -29,6 +29,9 @@ module.exports = 'editor:level-thang-done-editing': c.object {} + 'editor:thangs-edited': c.object {required: ['thangs']}, + thangs: c.array {}, {type: 'object'} + 'editor:level-loaded': c.object {required: ['level']}, level: {type: 'object'} diff --git a/app/schemas/subscriptions/god.coffee b/app/schemas/subscriptions/god.coffee index 5a8f0f09f..cd164806f 100644 --- a/app/schemas/subscriptions/god.coffee +++ b/app/schemas/subscriptions/god.coffee @@ -16,7 +16,7 @@ goalStatesSchema = {type: 'integer', minimum: 0} {type: 'string', enum: ['end']} ] - team: {type: ['null', 'string']} + team: {type: ['null', 'string', 'undefined']} worldUpdatedEventSchema = c.object {required: ['world', 'firstWorld', 'goalStates', 'team', 'firstChangedFrame']}, world: {type: 'object'} @@ -50,4 +50,4 @@ module.exports = 'god:debug-value-return': c.object {required: ['key']}, key: {type: 'string'} - value: {type: 'any'} + value: {type: ['any', 'undefined']} diff --git a/app/schemas/subscriptions/play.coffee b/app/schemas/subscriptions/play.coffee index 3575cc1fc..a19530bf3 100644 --- a/app/schemas/subscriptions/play.coffee +++ b/app/schemas/subscriptions/play.coffee @@ -140,7 +140,7 @@ module.exports = {type: 'integer', minimum: 0} {type: 'string', enum: ['end']} ] - team: {type: ['null', 'string']} + team: {type: ['null', 'string', 'undefined']} goals: c.array {}, {type: 'object'} overallStatus: diff --git a/app/schemas/subscriptions/surface.coffee b/app/schemas/subscriptions/surface.coffee index 22b59643a..418e8f784 100644 --- a/app/schemas/subscriptions/surface.coffee +++ b/app/schemas/subscriptions/surface.coffee @@ -38,19 +38,19 @@ module.exports = # /app/lib/surface 'sprite:speech-updated': c.object {required: ['sprite', 'thang']}, sprite: {type: 'object'} - thang: {type: 'object'} - blurb: {type: 'string'} + thang: {type: ['object', 'null']} + blurb: {type: ['string', 'null']} message: {type: 'string'} mood: {type: 'string'} - responses: {type: 'array'} + responses: {type: ['array', 'null', 'undefined']} spriteID: {type: 'string'} sound: {type: ['null', 'undefined', 'object']} 'level:sprite-dialogue': c.object {required: ['spriteID', 'message']}, - blurb: {type: 'string'} + blurb: {type: ['string', 'null']} message: {type: 'string'} mood: {type: 'string'} - responses: {type: 'array'} + responses: {type: ['array', 'null', 'undefined']} spriteID: {type: 'string'} sound: {type: ['null', 'undefined', 'object']} @@ -141,13 +141,13 @@ module.exports = # /app/lib/surface originalEvent: {type: 'object'} worldPos: {type: ['object', 'null', 'undefined']} - 'surface:stage-mouse-up': c.object {required: ['onBackground', 'x', 'y', 'originalEvent']}, + 'surface:stage-mouse-up': c.object {required: ['onBackground', 'originalEvent']}, onBackground: {type: 'boolean'} - x: {type: 'number'} - y: {type: 'number'} + x: {type: ['number', 'undefined']} + y: {type: ['number', 'undefined']} originalEvent: {type: 'object'} - 'surface:mouse-scrolled': c.object {required: ['deltaX', 'deltaY', 'screenPos', 'canvas']}, + 'surface:mouse-scrolled': c.object {required: ['deltaX', 'deltaY', 'canvas']}, deltaX: {type: 'number'} deltaY: {type: 'number'} screenPos: c.object {required: ['x', 'y']}, diff --git a/app/schemas/subscriptions/tome.coffee b/app/schemas/subscriptions/tome.coffee index f79cb62b5..ced070475 100644 --- a/app/schemas/subscriptions/tome.coffee +++ b/app/schemas/subscriptions/tome.coffee @@ -1,7 +1,7 @@ c = require 'schemas/schemas' module.exports = - 'tome:cast-spell': c.object {title: 'Cast Spell', description: 'Published when a spell is cast', required: ['spell', 'thang', 'preload', 'realTime']}, + 'tome:cast-spell': c.object {title: 'Cast Spell', description: 'Published when a spell is cast', required: []}, spell: {type: 'object'} thang: {type: 'object'} preload: {type: 'boolean'} @@ -106,4 +106,6 @@ module.exports = 'tome:focus-editor': c.object {title: 'Focus Editor', description: 'Published whenever we want to give focus back to the editor'} - 'tome:fullscreen-view': c.object {title: 'Fullscreen View', description: 'Published when we want to make the Tome take up most of the screen'} + 'tome:toggle-maximize': c.object {title: 'Toggle Maximize', description: 'Published when we want to make the Tome take up most of the screen'} + + 'tome:maximize-toggled': c.object {title: 'Maximize Toggled', description: 'Published when the Tome has changed maximize/minimize state.'} diff --git a/app/styles/editor/thang/thang-type-edit-view.sass b/app/styles/editor/thang/thang-type-edit-view.sass index 109651a3b..e39acc7f5 100644 --- a/app/styles/editor/thang/thang-type-edit-view.sass +++ b/app/styles/editor/thang/thang-type-edit-view.sass @@ -21,6 +21,11 @@ margin-left: 5px input[type="file"] display: none + + #thang-type-file-size + width: 350px + float: left + margin: 20px 10px .treema-row img max-width: 100% diff --git a/app/styles/play.sass b/app/styles/play.sass index 900207ca9..1a85cd747 100644 --- a/app/styles/play.sass +++ b/app/styles/play.sass @@ -40,6 +40,7 @@ margin-left: 20px h3 + margin-top: 0 margin-bottom: 0px .level-description diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass index 524b01db8..4a87c0099 100644 --- a/app/styles/play/level.sass +++ b/app/styles/play/level.sass @@ -73,7 +73,7 @@ body.is-playing right: 0 top: 0px bottom: 0 - transition: width 0.5s ease-in-out + @include transition(width 0.5s ease-in-out, right 0.5s ease-in-out) #pointer position: absolute @@ -186,9 +186,25 @@ body.is-playing color: white text-decoration: underline + #fullscreen-editor-background-screen + background-color: black + opacity: 0.5 + cursor: pointer + display: none + position: absolute + left: 0 + right: 0 + bottom: 0 + top: 0 + z-index: 19 + html.fullscreen-editor #level-view + #fullscreen-editor-background-screen + display: block + #code-area position: fixed - width: 100% + width: 95% height: 100% + right: 0 diff --git a/app/templates/editor/thang/thang-type-edit-view.jade b/app/templates/editor/thang/thang-type-edit-view.jade index 394c1f97a..9ec17d9d5 100644 --- a/app/templates/editor/thang/thang-type-edit-view.jade +++ b/app/templates/editor/thang/thang-type-edit-view.jade @@ -86,6 +86,7 @@ block outer_content span.glyphicon.glyphicon-remove span.spl Clear Data input#real-upload-button(type="file") + #thang-type-file-size= fileSizeString div#thang-type-treema .clearfix div#display-col.well diff --git a/app/templates/play.jade b/app/templates/play.jade index f8d07e62f..291cfa866 100644 --- a/app/templates/play.jade +++ b/app/templates/play.jade @@ -28,4 +28,11 @@ block content span(data-i18n="play.level_difficulty") Difficulty: each i in Array(level.difficulty) i.icon-star + - var playCount = levelPlayCountMap[level.id] + if playCount + 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 diff --git a/app/templates/play/ladder_home.jade b/app/templates/play/ladder_home.jade index e3b976ae6..a496919b8 100644 --- a/app/templates/play/ladder_home.jade +++ b/app/templates/play/ladder_home.jade @@ -19,5 +19,9 @@ block content span(data-i18n="play.level_difficulty") Difficulty: each i in Array(level.difficulty) | ★ + - var playCount = levelPlayCountMap[level.id] + if playCount + span.spl.spr - #{playCount.sessions} + span(data-i18n="play.players") players .play-text-container .overlay-text.play-text(data-i18n="home.play") Play diff --git a/app/templates/play/level.jade b/app/templates/play/level.jade index ac9b335f9..a0b5f8014 100644 --- a/app/templates/play/level.jade +++ b/app/templates/play/level.jade @@ -2,6 +2,8 @@ .level-content #control-bar-view + + #fullscreen-editor-background-screen(title="Click to minimize the code editor") #code-area #code-area-gradient.gradient diff --git a/app/templates/play/level/modal/keyboard_shortcuts.jade b/app/templates/play/level/modal/keyboard_shortcuts.jade index 41b67c8f8..06815d09e 100644 --- a/app/templates/play/level/modal/keyboard_shortcuts.jade +++ b/app/templates/play/level/modal/keyboard_shortcuts.jade @@ -5,15 +5,15 @@ block modal-header-content block modal-body-content dl.dl-horizontal - dt(title="Shift+" + enter) + dt(title=shift + "+" + enter) code ⇧+#{enter} dd(data-i18n="keyboard_shortcuts.cast_spell") Cast current spell. dl.dl-horizontal - dt(title=ctrlName + "+Shift+" + enter) + dt(title=ctrlName + "+" + shift + "+" + enter) code #{ctrl}+⇧+#{enter} dd(data-i18n="keyboard_shortcuts.run_real_time") Run in real time. dl.dl-horizontal - dt(title="Shift+" + space) + dt(title=shift + "+" + space) code ⇧+#{space} dd(data-i18n="keyboard_shortcuts.continue_script") Continue past current script. dl.dl-horizontal @@ -55,9 +55,13 @@ block modal-body-content code #{ctrl}+O dd(data-i18n="keyboard_shortcuts.toggle_pathfinding") Toggle pathfinding overlay. dl.dl-horizontal - dt(title=ctrlName + "+Shift+B") + dt(title=ctrlName + "+" + shift + "+B") code #{ctrl}+⇧+B dd(data-i18n="keyboard_shortcuts.beautify") Beautify your code by standardizing its formatting. + dl.dl-horizontal + dt(title=ctrlName + "+" + shift + "+M") + code #{ctrl}+⇧+M + dd(data-i18n="keyboard_shortcuts.maximize_editor") Maximize/minimize code editor. dl.dl-horizontal dt(title="Arrow keys") code ← diff --git a/app/templates/play/level/tome/spell_list_tab_entry.jade b/app/templates/play/level/tome/spell_list_tab_entry.jade index 02726b131..9e3ac0ce8 100644 --- a/app/templates/play/level/tome/spell_list_tab_entry.jade +++ b/app/templates/play/level/tome/spell_list_tab_entry.jade @@ -7,14 +7,14 @@ img(src="/images/level/code_editor_tab_background.png").spell-tab-image-hidden.h code #{methodSignature} .spell-tool-buttons - .btn.btn-small.fullscreen-code(title="Expand code editor") + .btn.btn-small.fullscreen-code(title=maximizeShortcutVerbose) i.icon-fullscreen i.icon-resize-small .btn.btn-small.reload-code(title="Reload original code for " + spell.name) i.icon-repeat - .btn.btn-small.beautify-code(title="Ctrl+Shift+B: Beautify code for " + spell.name) + .btn.btn-small.beautify-code(title=beautifyShortcutVerbose) i.icon-magnet .clearfix \ No newline at end of file diff --git a/app/views/editor/level/scripts/ScriptsTabView.coffee b/app/views/editor/level/scripts/ScriptsTabView.coffee index a7f066be0..b4e486de9 100644 --- a/app/views/editor/level/scripts/ScriptsTabView.coffee +++ b/app/views/editor/level/scripts/ScriptsTabView.coffee @@ -12,6 +12,7 @@ module.exports = class ScriptsTabView extends CocoView subscriptions: 'editor:level-loaded': 'onLevelLoaded' + 'editor:thangs-edited': 'onThangsEdited' constructor: (options) -> super options @@ -53,7 +54,7 @@ module.exports = class ScriptsTabView extends CocoView @selectedScriptPath = null return - thangIDs = @getThangIDs() + @thangIDs = @getThangIDs() treemaOptions = world: @world filePath: "db/level/#{@level.get('original')}" @@ -61,7 +62,7 @@ module.exports = class ScriptsTabView extends CocoView view: @ schema: Level.schema.properties.scripts.items data: selected.data - thangIDs: thangIDs + thangIDs: @thangIDs dimensions: @dimensions supermodel: @supermodel readOnly: me.get('anonymous') @@ -88,7 +89,7 @@ module.exports = class ScriptsTabView extends CocoView @selectedScriptPath = newPath getThangIDs: -> - (t.id for t in @level.get('thangs') when t.id isnt 'Interface') + (t.id for t in @level.get('thangs')) onNewScriptAdded: (scriptNode) => return unless scriptNode @@ -110,6 +111,11 @@ module.exports = class ScriptsTabView extends CocoView onScriptChanged: => @scriptsTreema.set(@selectedScriptPath, @scriptTreema.data) + onThangsEdited: (e) -> + # Update in-place so existing Treema nodes refer to the same array. + @thangIDs.splice(0, @thangIDs.length, @getThangIDs()...) + + class ScriptsNode extends TreemaArrayNode nodeDescription: 'Script' addNewChild: -> diff --git a/app/views/editor/level/settings/SettingsTabView.coffee b/app/views/editor/level/settings/SettingsTabView.coffee index dabca773a..c593e9120 100644 --- a/app/views/editor/level/settings/SettingsTabView.coffee +++ b/app/views/editor/level/settings/SettingsTabView.coffee @@ -18,6 +18,7 @@ module.exports = class SettingsTabView extends CocoView subscriptions: 'editor:level-loaded': 'onLevelLoaded' + 'editor:thangs-edited': 'onThangsEdited' constructor: (options) -> super options @@ -29,7 +30,7 @@ module.exports = class SettingsTabView extends CocoView schema = _.cloneDeep Level.schema schema.properties = _.pick schema.properties, (value, key) => key in @editableSettings schema.required = _.intersection schema.required, @editableSettings - thangIDs = @getThangIDs() + @thangIDs = @getThangIDs() treemaOptions = filePath: "db/level/#{@level.get('original')}" supermodel: @supermodel @@ -37,7 +38,7 @@ module.exports = class SettingsTabView extends CocoView data: data readOnly: me.get('anonymous') callbacks: {change: @onSettingsChanged} - thangIDs: thangIDs + thangIDs: @thangIDs nodeClasses: object: SettingsNode thang: nodes.ThangNode @@ -47,7 +48,7 @@ module.exports = class SettingsTabView extends CocoView @settingsTreema.open() getThangIDs: -> - (t.id for t in @level.get('thangs') when t.id isnt 'Interface') + (t.id for t in @level.get('thangs')) onSettingsChanged: (e) => $('.level-title').text @settingsTreema.data.name @@ -55,5 +56,10 @@ module.exports = class SettingsTabView extends CocoView continue if @settingsTreema.data[key] is undefined @level.set key, @settingsTreema.data[key] + onThangsEdited: (e) -> + # Update in-place so existing Treema nodes refer to the same array. + @thangIDs.splice(0, @thangIDs.length, @getThangIDs()...) + + class SettingsNode extends TreemaObjectNode nodeDescription: 'Settings' diff --git a/app/views/editor/level/thangs/ThangsTabView.coffee b/app/views/editor/level/thangs/ThangsTabView.coffee index 2ca8211e5..3745e2f8b 100644 --- a/app/views/editor/level/thangs/ThangsTabView.coffee +++ b/app/views/editor/level/thangs/ThangsTabView.coffee @@ -402,7 +402,7 @@ module.exports = class ThangsTabView extends CocoView thang.isSelectable = not thang.isLand for thang in @world.thangs # let us select walls and such @surface?.setWorld @world @selectAddThangType @addThangType, @cloneSourceThang if @addThangType # make another addThang sprite, since the World just refreshed - null + Backbone.Mediator.publish 'editor:thangs-edited', thangs: @world.thangs onTreemaThangSelected: (e, selectedTreemas) => selectedThangID = _.last(selectedTreemas)?.data.id diff --git a/app/views/editor/thang/ThangTypeEditView.coffee b/app/views/editor/thang/ThangTypeEditView.coffee index e54bb9274..afc14ef33 100644 --- a/app/views/editor/thang/ThangTypeEditView.coffee +++ b/app/views/editor/thang/ThangTypeEditView.coffee @@ -56,6 +56,7 @@ module.exports = class ThangTypeEditView extends RootView @thangType.saveBackups = true @listenToOnce @thangType, 'sync', -> @files = @supermodel.loadCollection(new DocumentFiles(@thangType), 'files').model + @updateFileSize() @refreshAnimation = _.debounce @refreshAnimation, 500 getRenderData: (context={}) -> @@ -64,6 +65,7 @@ module.exports = class ThangTypeEditView extends RootView context.animations = @getAnimationNames() context.authorized = not me.get('anonymous') context.recentlyPlayedLevels = storage.load('recently-played-levels') ? ['items'] + context.fileSizeString = @fileSizeString context getAnimationNames: -> @@ -197,6 +199,16 @@ module.exports = class ThangTypeEditView extends RootView @treema.set('raw', @thangType.get('raw')) @updateSelectBox() @refreshAnimation() + @updateFileSize() + + updateFileSize: -> + file = JSON.stringify(@thangType.attributes) + compressed = LZString.compress(file) + size = (file.length / 1024).toFixed(1) + "KB" + compressedSize = (compressed.length / 1024).toFixed(1) + "KB" + gzipCompressedSize = compressedSize * 1.65 # just based on comparing ogre barracks + @fileSizeString = "Size: #{size} (~#{compressedSize} gzipped)" + @$el.find('#thang-type-file-size').text @fileSizeString # animation select diff --git a/app/views/play/MainPlayView.coffee b/app/views/play/MainPlayView.coffee index bb3838327..34a27341c 100644 --- a/app/views/play/MainPlayView.coffee +++ b/app/views/play/MainPlayView.coffee @@ -18,293 +18,318 @@ module.exports = class MainPlayView extends RootView constructor: (options) -> super options @levelStatusMap = {} + @levelPlayCountMap = {} @sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', null, 0).model @listenToOnce @sessions, 'sync', @onSessionsLoaded + @getLevelPlayCounts() onSessionsLoaded: (e) -> for session in @sessions.models @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started' @render() + getLevelPlayCounts: -> + success = (levelPlayCounts) => + return if @destroyed + for level in levelPlayCounts + @levelPlayCountMap[level._id] = playtime: level.playtime, sessions: level.sessions + @render() if @supermodel.finished() + + levelIDs = [] + for campaign in campaigns + for level in campaign.levels + levelIDs.push level.id + levelPlayCountsRequest = @supermodel.addRequestResource 'play_counts', { + url: '/db/level/-/play_counts' + data: {ids: levelIDs} + method: 'POST' + success: success + }, 0 + levelPlayCountsRequest.load() + getRenderData: (context={}) -> context = super(context) - tutorials = [ - { - name: 'Rescue Mission' - difficulty: 1 - id: 'rescue-mission' - image: '/file/db/level/52740644904ac0411700067c/rescue_mission_icon.png' - description: 'Tharin has been captured!' - } - { - name: 'Grab the Mushroom' - difficulty: 1 - id: 'grab-the-mushroom' - image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' - description: 'Grab a powerup and smash a big ogre.' - } - { - name: 'Drink Me' - difficulty: 1 - id: 'drink-me' - image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png' - description: 'Drink up and slay two munchkins.' - } - { - name: 'Taunt the Guards' - difficulty: 1 - id: 'taunt-the-guards' - image: '/file/db/level/5276c9bdcf83207a2801ff8f/taunt_icon.png' - description: 'Tharin, if clever, can escape with Phoebe.' - } - { - name: 'It\'s a Trap' - difficulty: 1 - id: 'its-a-trap' - image: '/file/db/level/528aea2d7f37fc4e0700016b/its_a_trap_icon.png' - description: 'Organize a dungeon ambush with archers.' - } - { - name: 'Break the Prison' - difficulty: 1 - id: 'break-the-prison' - image: '/file/db/level/5275272c69abdcb12401216e/break_the_prison_icon.png' - description: 'More comrades are imprisoned!' - } - { - name: 'Taunt' - difficulty: 1 - id: 'taunt' - image: '/file/db/level/525f150306e1ab0962000018/taunt_icon.png' - description: 'Taunt the ogre to claim victory.' - } - { - name: 'Cowardly Taunt' - difficulty: 1 - id: 'cowardly-taunt' - image: '/file/db/level/525abfd9b12777d78e000009/cowardly_taunt_icon.png' - description: 'Lure infuriated ogres to their doom.' - } - { - name: 'Commanding Followers' - difficulty: 1 - id: 'commanding-followers' - image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' - description: 'Lead allied soldiers into battle.' - } - { - name: 'Mobile Artillery' - difficulty: 1 - id: 'mobile-artillery' - image: '/file/db/level/525085419851b83f4b000001/mobile_artillery_icon.png' - description: 'Blow ogres up!' - } - ] - - experienced = [ - { - name: 'Hunter Triplets' - difficulty: 2 - id: 'hunter-triplets' - image: '/file/db/level/526711d9add4f8965f000002/hunter_triplets_icon.png' - description: 'Three soldiers go ogre hunting.' - } - { - name: 'Emphasis on Aim' - difficulty: 2 - id: 'emphasis-on-aim' - image: '/file/db/level/525f384d96cd77000000000f/munchkin_masher_icon.png' - description: 'Choose your targets carefully.' - } - { - name: 'Zone of Danger' - difficulty: 3 - id: 'zone-of-danger' - image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' - description: 'Target the ogres swarming into arrow range.' - } - { - name: 'Molotov Medic' - difficulty: 2 - id: 'molotov-medic' - image: '/file/db/level/52602ecb026e8481e7000001/generic_1.png' - description: 'Tharin must play support in this dungeon battle.' - } - { - name: 'Gridmancer' - difficulty: 5 - id: 'gridmancer' - image: '/file/db/level/52ae2460ef42c52f13000008/gridmancer_icon.png' - description: 'Super algorithm challenge level!' - } - ] - - arenas = [ - { - name: 'Criss-Cross' - difficulty: 5 - id: 'criss-cross' - image: '/file/db/level/528aea2d7f37fc4e0700016b/its_a_trap_icon.png' - description: 'Participate in a bidding war with opponents to reach the other side!' - levelPath: 'ladder' - } - { - name: 'Greed' - difficulty: 4 - id: 'greed' - image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' - description: 'Liked Dungeon Arena and Gold Rush? Put them together in this economic arena!' - levelPath: 'ladder' - } - { - name: 'Dungeon Arena' - difficulty: 3 - id: 'dungeon-arena' - image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' - description: 'Play head-to-head against fellow Wizards in a dungeon melee!' - levelPath: 'ladder' - } - { - name: 'Gold Rush' - difficulty: 3 - id: 'gold-rush' - image: '/file/db/level/52602ecb026e8481e7000001/generic_1.png' - description: 'Prove you are better at collecting gold than your opponent!' - levelPath: 'ladder' - } - { - name: 'Brawlwood' - difficulty: 4 - id: 'brawlwood' - image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' - description: 'Combat the armies of other Wizards in a strategic forest arena! (Fast computer required.)' - levelPath: 'ladder' - } - { - name: 'Sky Span (Testing)' - difficulty: 3 - id: 'sky-span' - image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' - description: 'Preview version of an upgraded Dungeon Arena. Help us with hero balance before release!' - levelPath: 'ladder' - } - ] - - classicAlgorithms = [ - { - name: 'Bubble Sort Bootcamp Battle' - difficulty: 3 - id: 'bubble-sort-bootcamp-battle' - image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' - description: 'Write a bubble sort to organize your soldiers. - by Alexandru Caciulescu' - } - { - name: 'Ogres of Hanoi' - difficulty: 3 - id: 'ogres-of-hanoi' - image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' - description: 'Transfer a stack of ogres while preserving their honor. - by Alexandru Caciulescu' - } - { - name: 'Danger! Minefield' - difficulty: 3 - id: 'danger-minefield' - image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png' - description: 'Learn how to find prime numbers while defusing mines! - by Alexandru Caciulescu' - } - { - name: 'K-means++ Cluster Wars' - difficulty: 4 - id: 'k-means-cluster-wars' - image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' - description: 'Learn cluster analysis while leading armies into battle! - by Alexandru Caciulescu' - } - { - name: 'Quicksort the Spiral' - difficulty: 3 - id: 'quicksort-the-spiral' - image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' - description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu' - } - { - name: 'Minimax Tic-Tac-Toe' - difficulty: 4 - id: 'minimax-tic-tac-toe' - image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' - description: 'Learn how to make a game AI with the Minimax algorithm. - by Alexandru Caciulescu' - } - ] - - playerCreated = [ - { - name: 'Extra Extrapolation' - difficulty: 2 - id: 'extra-extrapolation' - image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png' - description: 'Predict your target\'s position for deadly aim. - by Sootn' - } - { - name: 'The Right Route' - difficulty: 1 - id: 'the-right-route' - image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' - description: 'Strike at the weak point in an array of enemies. - by Aftermath' - } - { - name: 'Sword Loop' - difficulty: 2 - id: 'sword-loop' - image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png' - description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja' - } - { - name: 'Coin Mania' - difficulty: 2 - id: 'coin-mania' - image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' - description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja' - } - { - name: 'Find the Spy' - difficulty: 2 - id: 'find-the-spy' - image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' - description: 'Identify the spies hidden among your soldiers - by Nathan Gossett' - } - { - name: 'Harvest Time' - difficulty: 2 - id: 'harvest-time' - image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' - description: 'Collect a hundred mushrooms in just five lines of code - by Nathan Gossett' - } - { - name: 'Guide Everyone Home' - difficulty: 2 - id: 'guide-everyone-home' - image: '/file/db/level/52740644904ac0411700067c/rescue_mission_icon.png' - description: 'Fetch the wizards teleporting into the area - by Nathan Gossett' - } - { - name: "Let's go Fly a Kite" - difficulty: 3 - id: 'lets-go-fly-a-kite' - image: '/file/db/level/526711d9add4f8965f000002/hunter_triplets_icon.png' - description: 'There is a horde of ogres marching on your village. Stay out of reach and use your bow to take them out! - by Danny Whittaker' - } - ] - - context.campaigns = [ - {id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials} - {id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas} - {id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced} - {id: 'classic' ,name: 'Classic Algorithms', description: '... in which you learn the most popular algorithms in Computer Science.', levels: classicAlgorithms} - {id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow Artisan Wizards.', levels: playerCreated} - ] + context.campaigns = campaigns context.levelStatusMap = @levelStatusMap + context.levelPlayCountMap = @levelPlayCountMap context afterRender: -> super() @$el.find('.modal').on 'shown.bs.modal', -> $('input:visible:first', @).focus() + + +tutorials = [ + { + name: 'Rescue Mission' + difficulty: 1 + id: 'rescue-mission' + image: '/file/db/level/52740644904ac0411700067c/rescue_mission_icon.png' + description: 'Tharin has been captured!' + } + { + name: 'Grab the Mushroom' + difficulty: 1 + id: 'grab-the-mushroom' + image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' + description: 'Grab a powerup and smash a big ogre.' + } + { + name: 'Drink Me' + difficulty: 1 + id: 'drink-me' + image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png' + description: 'Drink up and slay two munchkins.' + } + { + name: 'Taunt the Guards' + difficulty: 1 + id: 'taunt-the-guards' + image: '/file/db/level/5276c9bdcf83207a2801ff8f/taunt_icon.png' + description: 'Tharin, if clever, can escape with Phoebe.' + } + { + name: 'It\'s a Trap' + difficulty: 1 + id: 'its-a-trap' + image: '/file/db/level/528aea2d7f37fc4e0700016b/its_a_trap_icon.png' + description: 'Organize a dungeon ambush with archers.' + } + { + name: 'Break the Prison' + difficulty: 1 + id: 'break-the-prison' + image: '/file/db/level/5275272c69abdcb12401216e/break_the_prison_icon.png' + description: 'More comrades are imprisoned!' + } + { + name: 'Taunt' + difficulty: 1 + id: 'taunt' + image: '/file/db/level/525f150306e1ab0962000018/taunt_icon.png' + description: 'Taunt the ogre to claim victory.' + } + { + name: 'Cowardly Taunt' + difficulty: 1 + id: 'cowardly-taunt' + image: '/file/db/level/525abfd9b12777d78e000009/cowardly_taunt_icon.png' + description: 'Lure infuriated ogres to their doom.' + } + { + name: 'Commanding Followers' + difficulty: 1 + id: 'commanding-followers' + image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' + description: 'Lead allied soldiers into battle.' + } + { + name: 'Mobile Artillery' + difficulty: 1 + id: 'mobile-artillery' + image: '/file/db/level/525085419851b83f4b000001/mobile_artillery_icon.png' + description: 'Blow ogres up!' + } +] + +experienced = [ + { + name: 'Hunter Triplets' + difficulty: 2 + id: 'hunter-triplets' + image: '/file/db/level/526711d9add4f8965f000002/hunter_triplets_icon.png' + description: 'Three soldiers go ogre hunting.' + } + { + name: 'Emphasis on Aim' + difficulty: 2 + id: 'emphasis-on-aim' + image: '/file/db/level/525f384d96cd77000000000f/munchkin_masher_icon.png' + description: 'Choose your targets carefully.' + } + { + name: 'Zone of Danger' + difficulty: 3 + id: 'zone-of-danger' + image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' + description: 'Target the ogres swarming into arrow range.' + } + { + name: 'Molotov Medic' + difficulty: 2 + id: 'molotov-medic' + image: '/file/db/level/52602ecb026e8481e7000001/generic_1.png' + description: 'Tharin must play support in this dungeon battle.' + } + { + name: 'Gridmancer' + difficulty: 5 + id: 'gridmancer' + image: '/file/db/level/52ae2460ef42c52f13000008/gridmancer_icon.png' + description: 'Super algorithm challenge level!' + } +] + +arenas = [ + { + name: 'Criss-Cross' + difficulty: 5 + id: 'criss-cross' + image: '/file/db/level/528aea2d7f37fc4e0700016b/its_a_trap_icon.png' + description: 'Participate in a bidding war with opponents to reach the other side!' + levelPath: 'ladder' + } + { + name: 'Greed' + difficulty: 4 + id: 'greed' + image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' + description: 'Liked Dungeon Arena and Gold Rush? Put them together in this economic arena!' + levelPath: 'ladder' + } + { + name: 'Dungeon Arena' + difficulty: 3 + id: 'dungeon-arena' + image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' + description: 'Play head-to-head against fellow Wizards in a dungeon melee!' + levelPath: 'ladder' + } + { + name: 'Gold Rush' + difficulty: 3 + id: 'gold-rush' + image: '/file/db/level/52602ecb026e8481e7000001/generic_1.png' + description: 'Prove you are better at collecting gold than your opponent!' + levelPath: 'ladder' + } + { + name: 'Brawlwood' + difficulty: 4 + id: 'brawlwood' + image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' + description: 'Combat the armies of other Wizards in a strategic forest arena! (Fast computer required.)' + levelPath: 'ladder' + } + { + name: 'Sky Span (Testing)' + difficulty: 3 + id: 'sky-span' + image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' + description: 'Preview version of an upgraded Dungeon Arena. Help us with hero balance before release!' + levelPath: 'ladder' + } +] + +classicAlgorithms = [ + { + name: 'Bubble Sort Bootcamp Battle' + difficulty: 3 + id: 'bubble-sort-bootcamp-battle' + image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' + description: 'Write a bubble sort to organize your soldiers. - by Alexandru Caciulescu' + } + { + name: 'Ogres of Hanoi' + difficulty: 3 + id: 'ogres-of-hanoi' + image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' + description: 'Transfer a stack of ogres while preserving their honor. - by Alexandru Caciulescu' + } + { + name: 'Danger! Minefield' + difficulty: 3 + id: 'danger-minefield' + image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png' + description: 'Learn how to find prime numbers while defusing mines! - by Alexandru Caciulescu' + } + { + name: 'K-means++ Cluster Wars' + difficulty: 4 + id: 'k-means-cluster-wars' + image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' + description: 'Learn cluster analysis while leading armies into battle! - by Alexandru Caciulescu' + } + { + name: 'Quicksort the Spiral' + difficulty: 3 + id: 'quicksort-the-spiral' + image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' + description: 'Learn Quicksort while sorting a spiral of ogres! - by Alexandru Caciulescu' + } + { + name: 'Minimax Tic-Tac-Toe' + difficulty: 4 + id: 'minimax-tic-tac-toe' + image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png' + description: 'Learn how to make a game AI with the Minimax algorithm. - by Alexandru Caciulescu' + } +] + +playerCreated = [ + { + name: 'Extra Extrapolation' + difficulty: 2 + id: 'extra-extrapolation' + image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png' + description: 'Predict your target\'s position for deadly aim. - by Sootn' + } + { + name: 'The Right Route' + difficulty: 1 + id: 'the-right-route' + image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png' + description: 'Strike at the weak point in an array of enemies. - by Aftermath' + } + { + name: 'Sword Loop' + difficulty: 2 + id: 'sword-loop' + image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png' + description: 'Kill the ogres and save the peasants with for-loops. - by Prabh Simran Singh Baweja' + } + { + name: 'Coin Mania' + difficulty: 2 + id: 'coin-mania' + image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' + description: 'Learn while-loops to grab coins and potions. - by Prabh Simran Singh Baweja' + } + { + name: 'Find the Spy' + difficulty: 2 + id: 'find-the-spy' + image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png' + description: 'Identify the spies hidden among your soldiers - by Nathan Gossett' + } + { + name: 'Harvest Time' + difficulty: 2 + id: 'harvest-time' + image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png' + description: 'Collect a hundred mushrooms in just five lines of code - by Nathan Gossett' + } + { + name: 'Guide Everyone Home' + difficulty: 2 + id: 'guide-everyone-home' + image: '/file/db/level/52740644904ac0411700067c/rescue_mission_icon.png' + description: 'Fetch the wizards teleporting into the area - by Nathan Gossett' + } + { + name: "Let's go Fly a Kite" + difficulty: 3 + id: 'lets-go-fly-a-kite' + image: '/file/db/level/526711d9add4f8965f000002/hunter_triplets_icon.png' + description: 'There is a horde of ogres marching on your village. Stay out of reach and use your bow to take them out! - by Danny Whittaker' + } +] + +campaigns = [ + {id: 'beginner', name: 'Beginner Campaign', description: '... in which you learn the wizardry of programming.', levels: tutorials} + {id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas} + {id: 'dev', name: 'Random Harder Levels', description: '... in which you learn the interface while doing something a little harder.', levels: experienced} + {id: 'classic' ,name: 'Classic Algorithms', description: '... in which you learn the most popular algorithms in Computer Science.', levels: classicAlgorithms} + {id: 'player_created', name: 'Player-Created', description: '... in which you battle against the creativity of your fellow Artisan Wizards.', levels: playerCreated} +] diff --git a/app/views/play/ladder/MainLadderView.coffee b/app/views/play/ladder/MainLadderView.coffee index 2ebd4bba1..00907172c 100644 --- a/app/views/play/ladder/MainLadderView.coffee +++ b/app/views/play/ladder/MainLadderView.coffee @@ -18,68 +18,93 @@ module.exports = class LadderHomeView extends RootView constructor: (options) -> super options @levelStatusMap = {} + @levelPlayCountMap = {} @sessions = @supermodel.loadCollection(new LevelSessionsCollection(), 'your_sessions', null, 0).model @listenToOnce @sessions, 'sync', @onSessionsLoaded + @getLevelPlayCounts() onSessionsLoaded: (e) -> for session in @sessions.models @levelStatusMap[session.get('levelID')] = if session.get('state')?.complete then 'complete' else 'started' @render() + getLevelPlayCounts: -> + success = (levelPlayCounts) => + return if @destroyed + for level in levelPlayCounts + @levelPlayCountMap[level._id] = playtime: level.playtime, sessions: level.sessions + @render() if @supermodel.finished() + + levelIDs = [] + for campaign in campaigns + for level in campaign.levels + levelIDs.push level.id + levelPlayCountsRequest = @supermodel.addRequestResource 'play_counts', { + url: '/db/level/-/play_counts' + data: {ids: levelIDs} + method: 'POST' + success: success + }, 0 + levelPlayCountsRequest.load() + getRenderData: (context={}) -> context = super(context) - arenas = [ - { - name: 'Criss-Cross' - difficulty: 5 - id: 'criss-cross' - image: '/file/db/level/5391f3d519dc22b8082159b2/banner2.png' - description: 'Participate in a bidding war with opponents to reach the other side!' - } - { - name: 'Greed' - difficulty: 4 - id: 'greed' - image: '/file/db/level/53558b5a9914f5a90d7ccddb/greed_banner.jpg' - description: 'Liked Dungeon Arena and Gold Rush? Put them together in this economic arena!' - } - { - name: 'Sky Span (Testing)' - difficulty: 3 - id: 'sky-span' - image: '/file/db/level/53c80fce0ddbef000084c667/sky-Span-banner.jpg' - description: 'Preview version of an upgraded Dungeon Arena. Help us with hero balance before release!' - } - { - name: 'Dungeon Arena' - difficulty: 3 - id: 'dungeon-arena' - image: '/file/db/level/53173f76c269d400000543c2/Level%20Banner%20Dungeon%20Arena.jpg' - description: 'Play head-to-head against fellow Wizards in a dungeon melee!' - } - { - name: 'Gold Rush' - difficulty: 3 - id: 'gold-rush' - image: '/file/db/level/533353722a61b7ca6832840c/Gold-Rush.png' - description: 'Prove you are better at collecting gold than your opponent!' - } - { - name: 'Brawlwood' - difficulty: 4 - id: 'brawlwood' - image: '/file/db/level/52d97ecd32362bc86e004e87/Level%20Banner%20Brawlwood.jpg' - description: 'Combat the armies of other Wizards in a strategic forest arena! (Fast computer required.)' - } - ] - - context.campaigns = [ - {id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas} - ] context.levelStatusMap = @levelStatusMap + context.levelPlayCountMap = @levelPlayCountMap + context.campaigns = campaigns context afterRender: -> super() @$el.find('.modal').on 'shown.bs.modal', -> $('input:visible:first', @).focus() + + +arenas = [ + { + name: 'Criss-Cross' + difficulty: 5 + id: 'criss-cross' + image: '/file/db/level/5391f3d519dc22b8082159b2/banner2.png' + description: 'Participate in a bidding war with opponents to reach the other side!' + } + { + name: 'Greed' + difficulty: 4 + id: 'greed' + image: '/file/db/level/53558b5a9914f5a90d7ccddb/greed_banner.jpg' + description: 'Liked Dungeon Arena and Gold Rush? Put them together in this economic arena!' + } + { + name: 'Sky Span (Testing)' + difficulty: 3 + id: 'sky-span' + image: '/file/db/level/53c80fce0ddbef000084c667/sky-Span-banner.jpg' + description: 'Preview version of an upgraded Dungeon Arena. Help us with hero balance before release!' + } + { + name: 'Dungeon Arena' + difficulty: 3 + id: 'dungeon-arena' + image: '/file/db/level/53173f76c269d400000543c2/Level%20Banner%20Dungeon%20Arena.jpg' + description: 'Play head-to-head against fellow Wizards in a dungeon melee!' + } + { + name: 'Gold Rush' + difficulty: 3 + id: 'gold-rush' + image: '/file/db/level/533353722a61b7ca6832840c/Gold-Rush.png' + description: 'Prove you are better at collecting gold than your opponent!' + } + { + name: 'Brawlwood' + difficulty: 4 + id: 'brawlwood' + image: '/file/db/level/52d97ecd32362bc86e004e87/Level%20Banner%20Brawlwood.jpg' + description: 'Combat the armies of other Wizards in a strategic forest arena! (Fast computer required.)' + } +] + +campaigns = [ + {id: 'multiplayer', name: 'Multiplayer Arenas', description: '... in which you code head-to-head against other players.', levels: arenas} +] diff --git a/app/views/play/level/LevelHUDView.coffee b/app/views/play/level/LevelHUDView.coffee index ba37fc42c..66d38c6d6 100644 --- a/app/views/play/level/LevelHUDView.coffee +++ b/app/views/play/level/LevelHUDView.coffee @@ -31,7 +31,7 @@ module.exports = class LevelHUDView extends CocoView @$el.addClass 'no-selection' onClick: (e) -> - Backbone.Mediator.publish 'tome:focus-editor' unless $(e.target).parents('.thang-props').length + Backbone.Mediator.publish 'tome:focus-editor', {} unless $(e.target).parents('.thang-props').length onFrameChanged: (e) -> @timeProgress = e.progress @@ -207,10 +207,10 @@ module.exports = class LevelHUDView extends CocoView if @lastResponses buttons = $('.enter button') for response, i in @lastResponses - f = (r) => => setTimeout((-> Backbone.Mediator.publish(r.channel, r.event)), 10) + f = (r) => => setTimeout((-> Backbone.Mediator.publish(r.channel, r.event or {})), 10) $(buttons[i]).click(f(response)) else - $('.enter', @bubble).click(-> Backbone.Mediator.publish('script:end-current-script')) + $('.enter', @bubble).click(-> Backbone.Mediator.publish('script:end-current-script', {})) return @animator.tick() @@ -221,7 +221,7 @@ module.exports = class LevelHUDView extends CocoView # If we decide that always having the last one fire is bad, we should make it smarter. return unless @lastResponses?.length r = @lastResponses[@lastResponses.length - 1] - _.delay (-> Backbone.Mediator.publish(r.channel, r.event)), 10 + _.delay (-> Backbone.Mediator.publish(r.channel, r.event or {})), 10 onEscapePressed: (e) -> @escapePressed = true diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index 246f10cab..71981514f 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -68,6 +68,7 @@ module.exports = class PlayLevelView extends RootView events: 'click #level-done-button': 'onDonePressed' + 'click #fullscreen-editor-background-screen': (e) -> Backbone.Mediator.publish 'tome:toggle-maximize', {} shortcuts: 'ctrl+s': 'onCtrlS' diff --git a/app/views/play/level/modal/KeyboardShortcutsModal.coffee b/app/views/play/level/modal/KeyboardShortcutsModal.coffee index 41c15df9d..53d29d5b3 100644 --- a/app/views/play/level/modal/KeyboardShortcutsModal.coffee +++ b/app/views/play/level/modal/KeyboardShortcutsModal.coffee @@ -14,4 +14,5 @@ module.exports = class KeyboardShortcutsModal extends ModalView c.enter = $.i18n.t 'keyboard_shortcuts.enter' c.space = $.i18n.t 'keyboard_shortcuts.space' c.escapeKey = $.i18n.t 'keyboard_shortcuts.escape' + c.shift = $.i18n.t 'keyboard_shortcuts.shift' c diff --git a/app/views/play/level/tome/SpellDebugView.coffee b/app/views/play/level/tome/SpellDebugView.coffee index efb27e0a4..2ba0ae767 100644 --- a/app/views/play/level/tome/SpellDebugView.coffee +++ b/app/views/play/level/tome/SpellDebugView.coffee @@ -171,7 +171,7 @@ module.exports = class SpellDebugView extends CocoView @frameRate = e.world.frameRate onFrameChanged: (data) -> - @currentFrame = data.frame + @currentFrame = Math.round(data.frame) @frameRate = data.world.frameRate onSpellChangedCalculation: (data) -> diff --git a/app/views/play/level/tome/SpellListTabEntryView.coffee b/app/views/play/level/tome/SpellListTabEntryView.coffee index 93e06f548..744b9d465 100644 --- a/app/views/play/level/tome/SpellListTabEntryView.coffee +++ b/app/views/play/level/tome/SpellListTabEntryView.coffee @@ -16,19 +16,23 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView 'tome:spell-changed': 'onSpellChanged' 'god:new-world-created': 'onNewWorld' 'tome:spell-changed-language': 'onSpellChangedLanguage' - 'tome:fullscreen-view': 'onFullscreenClick' + 'tome:toggle-maximize': 'onToggleMaximize' events: 'click .spell-list-button': 'onDropdownClick' 'click .reload-code': 'onCodeReload' 'click .beautify-code': 'onBeautifyClick' - 'click .fullscreen-code': 'onFullscreenClick' + 'click .fullscreen-code': 'onToggleMaximize' constructor: (options) -> super options getRenderData: (context={}) -> context = super context + ctrl = if @isMac() then 'Cmd' else 'Ctrl' + shift = $.i18n.t 'keyboard_shortcuts.shift' + context.beautifyShortcutVerbose = "#{ctrl}+#{shift}+B: #{$.i18n.t 'keyboard_shortcuts.beautify'}" + context.maximizeShortcutVerbose = "#{ctrl}+#{shift}+M: #{$.i18n.t 'keyboard_shortcuts.maximize_editor'}" context afterRender: -> @@ -86,19 +90,20 @@ module.exports = class SpellListTabEntryView extends SpellListEntryView return unless @controlsEnabled Backbone.Mediator.publish 'tome:toggle-spell-list', {} - onCodeReload: -> + onCodeReload: (e) -> return unless @controlsEnabled Backbone.Mediator.publish 'tome:reload-code', spell: @spell - onBeautifyClick: -> + onBeautifyClick: (e) -> return unless @controlsEnabled Backbone.Mediator.publish 'tome:spell-beautify', spell: @spell - onFullscreenClick: -> + onToggleMaximize: (e) -> $codearea = $('html') $('#code-area').css 'z-index', 20 unless $codearea.hasClass 'fullscreen-editor' $('html').toggleClass 'fullscreen-editor' $('.fullscreen-code').toggleClass 'maximized' + Backbone.Mediator.publish 'tome:maximize-toggled', {} updateReloadButton: -> changed = @spell.hasChanged null, @spell.getSource() diff --git a/app/views/play/level/tome/SpellView.coffee b/app/views/play/level/tome/SpellView.coffee index d23212a5d..e5cded4f7 100644 --- a/app/views/play/level/tome/SpellView.coffee +++ b/app/views/play/level/tome/SpellView.coffee @@ -50,6 +50,7 @@ module.exports = class SpellView extends CocoView 'tome:update-snippets': 'addZatannaSnippets' 'tome:insert-snippet': 'onInsertSnippet' 'tome:spell-beautify': 'onSpellBeautify' + 'tome:maximize-toggled': 'onMaximizeToggled' 'script:state-changed': 'onScriptStateChange' events: @@ -181,8 +182,8 @@ module.exports = class SpellView extends CocoView exec: -> # just prevent default ACE go-to-line alert addCommand name: 'open-fullscreen-editor' - bindKey: {win: 'Alt-Shift-F', mac: 'Ctrl-Shift-F'} - exec: -> Backbone.Mediator.publish 'tome:fullscreen-view', {} + bindKey: {win: 'Ctrl-Shift-M', mac: 'Command-Shift-M|Ctrl-Shift-M'} + exec: -> Backbone.Mediator.publish 'tome:toggle-maximize', {} fillACE: -> @ace.setValue @spell.source @@ -665,6 +666,9 @@ module.exports = class SpellView extends CocoView pretty = @spellThang.aether.beautify ugly @ace.setValue pretty + onMaximizeToggled: (e) -> + @ace.resize true + onChangeEditorConfig: (e) -> aceConfig = me.get('aceConfig') ? {} @ace.setDisplayIndentGuides aceConfig.indentGuides # default false diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index 76d0ed8ac..79946b116 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -42,6 +42,7 @@ LevelHandler = class LevelHandler extends Handler return @getLeaderboardGPlusFriends(req, res, args[0]) if args[1] is 'leaderboard_gplus_friends' return @getHistogramData(req, res, args[0]) if args[1] is 'histogram_data' return @checkExistence(req, res, args[0]) if args[1] is 'exists' + return @getPlayCountsBySlugs(req, res) if args[1] is 'play_counts' super(arguments...) fetchLevelByIDAndHandleErrors: (id, req, res, callback) -> @@ -278,4 +279,27 @@ LevelHandler = class LevelHandler extends Handler return @sendNotFoundError(res) unless doc? @sendSuccess(res, doc) + getPlayCountsBySlugs: (req, res) -> + # This is hella slow (4s on my box), so relying on some dumb caching for it. + # If we can't make this faster with indexing or something, we might want to maintain the counts another way. + levelIDs = req.query.ids or req.body.ids + @playCountCache ?= {} + @playCountCachedSince ?= new Date() + if (new Date()) - @playCountCachedSince > 86400 * 1000 # Dumb cache expiration + @playCountCache = {} + @playCountCacheSince = new Date() + cacheKey = levelIDs.join ',' + if playCounts = @playCountCache[cacheKey] + return @sendSuccess res, playCounts + query = Session.aggregate [ + {$match: {levelID: {$in: levelIDs}}}, + {$group: {_id: "$levelID", playtime: {$sum: "$playtime"}, sessions: {$sum: 1}}}, + {$sort: {sessions: -1}} + ] + query.exec (err, data) => + if err? then return @sendDatabaseError res, err + @playCountCache[cacheKey] = data + @sendSuccess res, data + + module.exports = new LevelHandler() diff --git a/vendor/scripts/lz-string-1.3.3-min.js b/vendor/scripts/lz-string-1.3.3-min.js new file mode 100644 index 000000000..1526d9e09 --- /dev/null +++ b/vendor/scripts/lz-string-1.3.3-min.js @@ -0,0 +1 @@ +var LZString={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_f:String.fromCharCode,compressToBase64:function(e){if(e==null)return"";var t="";var n,r,i,s,o,u,a;var f=0;e=LZString.compress(e);while(f>8;r=e.charCodeAt(f/2)&255;if(f/2+1>8;else i=NaN}else{n=e.charCodeAt((f-1)/2)&255;if((f+1)/2>8;i=e.charCodeAt((f+1)/2)&255}else r=i=NaN}f+=3;s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+LZString._keyStr.charAt(s)+LZString._keyStr.charAt(o)+LZString._keyStr.charAt(u)+LZString._keyStr.charAt(a)}return t},decompressFromBase64:function(e){if(e==null)return"";var t="",n=0,r,i,s,o,u,a,f,l,c=0,h=LZString._f;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(c>4;s=(a&15)<<4|f>>2;o=(f&3)<<6|l;if(n%2==0){r=i<<8;if(f!=64){t+=h(r|s)}if(l!=64){r=o<<8}}else{t=t+h(r|i);if(f!=64){r=s<<8}if(l!=64){t+=h(r|o)}}n+=3}return LZString.decompress(t)},compressToUTF16:function(e){if(e==null)return"";var t="",n,r,i,s=0,o=LZString._f;e=LZString.compress(e);for(n=0;n>1)+32);i=(r&1)<<14;break;case 1:t+=o(i+(r>>2)+32);i=(r&3)<<13;break;case 2:t+=o(i+(r>>3)+32);i=(r&7)<<12;break;case 3:t+=o(i+(r>>4)+32);i=(r&15)<<11;break;case 4:t+=o(i+(r>>5)+32);i=(r&31)<<10;break;case 5:t+=o(i+(r>>6)+32);i=(r&63)<<9;break;case 6:t+=o(i+(r>>7)+32);i=(r&127)<<8;break;case 7:t+=o(i+(r>>8)+32);i=(r&255)<<7;break;case 8:t+=o(i+(r>>9)+32);i=(r&511)<<6;break;case 9:t+=o(i+(r>>10)+32);i=(r&1023)<<5;break;case 10:t+=o(i+(r>>11)+32);i=(r&2047)<<4;break;case 11:t+=o(i+(r>>12)+32);i=(r&4095)<<3;break;case 12:t+=o(i+(r>>13)+32);i=(r&8191)<<2;break;case 13:t+=o(i+(r>>14)+32);i=(r&16383)<<1;break;case 14:t+=o(i+(r>>15)+32,(r&32767)+32);s=0;break}}return t+o(i+32)},decompressFromUTF16:function(e){if(e==null)return"";var t="",n,r,i=0,s=0,o=LZString._f;while(s>14);n=(r&16383)<<2;break;case 2:t+=o(n|r>>13);n=(r&8191)<<3;break;case 3:t+=o(n|r>>12);n=(r&4095)<<4;break;case 4:t+=o(n|r>>11);n=(r&2047)<<5;break;case 5:t+=o(n|r>>10);n=(r&1023)<<6;break;case 6:t+=o(n|r>>9);n=(r&511)<<7;break;case 7:t+=o(n|r>>8);n=(r&255)<<8;break;case 8:t+=o(n|r>>7);n=(r&127)<<9;break;case 9:t+=o(n|r>>6);n=(r&63)<<10;break;case 10:t+=o(n|r>>5);n=(r&31)<<11;break;case 11:t+=o(n|r>>4);n=(r&15)<<12;break;case 12:t+=o(n|r>>3);n=(r&7)<<13;break;case 13:t+=o(n|r>>2);n=(r&3)<<14;break;case 14:t+=o(n|r>>1);n=(r&1)<<15;break;case 15:t+=o(n|r);i=0;break}s++}return LZString.decompress(t)},compress:function(e){if(e==null)return"";var t,n,r={},i={},s="",o="",u="",a=2,f=3,l=2,c="",h=0,p=0,d,v=LZString._f;for(d=0;d>1}}else{n=1;for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}r[o]=f++;u=String(s)}}if(u!==""){if(Object.prototype.hasOwnProperty.call(i,u)){if(u.charCodeAt(0)<256){for(t=0;t>1}}else{n=1;for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}}n=2;for(t=0;t>1}while(true){h=h<<1;if(p==15){c+=v(h);break}else p++}return c},decompress:function(e){if(e==null)return"";if(e=="")return null;var t=[],n,r=4,i=4,s=3,o="",u="",a,f,l,c,h,p,d,v=LZString._f,m={string:e,val:e.charCodeAt(0),position:32768,index:1};for(a=0;a<3;a+=1){t[a]=a}l=0;h=Math.pow(2,2);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(n=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 2:return""}t[3]=d;f=u=d;while(true){if(m.index>m.string.length){return""}l=0;h=Math.pow(2,s);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(d=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 2:return u}if(r==0){r=Math.pow(2,s);s++}if(t[d]){o=t[d]}else{if(d===i){o=f+f.charAt(0)}else{return null}}u+=o;t[i++]=f+o.charAt(0);r--;f=o;if(r==0){r=Math.pow(2,s);s++}}}};if(typeof module!=="undefined"&&module!=null){module.exports=LZString}