diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee index da87a1221..fcefb97fe 100644 --- a/app/lib/LevelLoader.coffee +++ b/app/lib/LevelLoader.coffee @@ -94,7 +94,7 @@ module.exports = class LevelLoader extends CocoClass heroConfig ?= me.get('heroConfig') heroConfig ?= {inventory: {}, thangType: '529ffbf1cf1818f2be000001'} # If we got here not from PlayLevelModal (like level editor preview), assign Tharin as the hero. session.set 'heroConfig', heroConfig unless _.isEqual heroConfig, session.get('heroConfig') - url = "/db/thang.type/#{heroConfig.thangType}/version?project=name,components,original" + url = "/db/thang.type/#{heroConfig.thangType}/version" @worldNecessities.push @maybeLoadURL(url, ThangType, 'thang') for itemThangType in _.values(heroConfig.inventory) diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 85fd293b7..1a4f254d5 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -34,7 +34,7 @@ module.exports = Surface = class Surface extends CocoClass currentFrame: 0 lastFrame: null totalFramesDrawn: 0 - playing: false # play vs. pause + playing: false # play vs. pause -- match default button state in playback.jade dead: false # if we kill it for some reason imagesLoaded: false worldLoaded: false @@ -204,6 +204,7 @@ module.exports = Surface = class Surface extends CocoClass if @mouseInBounds isnt mib Backbone.Mediator.publish('surface:mouse-' + (if mib then 'over' else 'out'), {}) @mouseInBounds = mib + @mouseIsDown = false restoreWorldState: -> frame = @world.getFrame(@getCurrentFrame()) @@ -218,6 +219,8 @@ module.exports = Surface = class Surface extends CocoClass updateState: (frameChanged) -> # world state must have been restored in @restoreWorldState + if @playing and @heroSprite and not @mouseIsDown and @camera.newTarget isnt @heroSprite.imageObject and @camera.target isnt @heroSprite.imageObject + @camera.zoomTo @heroSprite.imageObject, @camera.zoom, 750 @camera.updateZoom() @spriteBoss.update frameChanged @dimmer?.setSprites @spriteBoss.sprites @@ -464,12 +467,14 @@ module.exports = Surface = class Surface extends CocoClass event = onBackground: onBackground, x: e.stageX, y: e.stageY, originalEvent: e, worldPos: wop Backbone.Mediator.publish 'surface:stage-mouse-down', event Backbone.Mediator.publish 'tome:focus-editor', {} + @mouseIsDown = true onMouseUp: (e) => return if @disabled 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 Backbone.Mediator.publish 'tome:focus-editor', {} + @mouseIsDown = false onMouseWheel: (e) => # https://github.com/brandonaaron/jquery-mousewheel @@ -483,7 +488,6 @@ module.exports = Surface = class Surface extends CocoClass Backbone.Mediator.publish 'surface:mouse-scrolled', event unless @disabled - #- Canvas callbacks onResize: (e) => @@ -515,6 +519,10 @@ module.exports = Surface = class Surface extends CocoClass @camera.onResize newWidth, newHeight + #- Camera focus on hero + focusOnHero: -> + @heroSprite = @spriteBoss.spriteFor 'Hero Placeholder' + #- Real-time playback diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 405ab7ec9..bf5299ac6 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -383,6 +383,7 @@ guide: "Guide" restart: "Restart" goals: "Goals" + goal: "Goal" success: "Success!" incomplete: "Incomplete" timed_out: "Ran out of time" diff --git a/app/models/Level.coffee b/app/models/Level.coffee index 348091b6a..1851bfbe1 100644 --- a/app/models/Level.coffee +++ b/app/models/Level.coffee @@ -24,7 +24,7 @@ module.exports = class Level extends CocoModel # Figure out ThangTypes' Components tmap = {} tmap[t.thangType] = true for t in o.thangs ? [] - o.thangTypes = (original: tt.get('original'), name: tt.get('name'), components: $.extend(true, [], tt.get('components')) for tt in supermodel.getModels ThangType when tmap[tt.get('original')] or tt.get('components')) + o.thangTypes = (original: tt.get('original'), name: tt.get('name'), components: $.extend(true, [], tt.get('components')) for tt in supermodel.getModels ThangType when tmap[tt.get('original')] or (tt.get('components') and not tt.notInLevel)) @sortThangComponents o.thangTypes, o.levelComponents, 'ThangType' @fillInDefaultComponentConfiguration o.thangTypes, o.levelComponents diff --git a/app/templates/play/level.jade b/app/templates/play/level.jade index a0b5f8014..ab6dc5fd4 100644 --- a/app/templates/play/level.jade +++ b/app/templates/play/level.jade @@ -13,11 +13,11 @@ canvas(width=924, height=589)#surface #canvas-left-gradient.gradient #canvas-top-gradient.gradient - #goals-view.secret + #goals-view - #level-flags-view.secret + #level-flags-view - #gold-view.secret.expanded + #gold-view #level-chat-view diff --git a/app/templates/play/level/playback.jade b/app/templates/play/level/playback.jade index f0c83859b..5502f78cb 100644 --- a/app/templates/play/level/playback.jade +++ b/app/templates/play/level/playback.jade @@ -1,4 +1,4 @@ -button.btn.btn-xs.btn-inverse#play-button.playing(title="Ctrl/Cmd + P: Toggle level play/pause") +button.btn.btn-xs.btn-inverse#play-button.paused(title="Ctrl/Cmd + P: Toggle level play/pause") i.icon-play.icon-white.big i.icon-pause.icon-white.big i.icon-repeat.icon-white.big diff --git a/app/views/game-menu/InventoryView.coffee b/app/views/game-menu/InventoryView.coffee index 317fef60e..a07b419d2 100644 --- a/app/views/game-menu/InventoryView.coffee +++ b/app/views/game-menu/InventoryView.coffee @@ -40,6 +40,7 @@ module.exports = class InventoryView extends CocoView onLoaded: -> @items.models = _.filter(@items.models, (item) => item.get('original') in @allowedItems) if @allowedItems + item.notInLevel = true for item in @items.models super() getRenderData: (context={}) -> diff --git a/app/views/play/level/LevelFlagsView.coffee b/app/views/play/level/LevelFlagsView.coffee index 971095ffe..d3e46a2b8 100644 --- a/app/views/play/level/LevelFlagsView.coffee +++ b/app/views/play/level/LevelFlagsView.coffee @@ -8,6 +8,7 @@ multiplayerFlagDelay = 0.5 # Long, static second delay for now; should be more module.exports = class LevelFlagsView extends CocoView id: 'level-flags-view' template: template + className: 'secret' subscriptions: 'playback:real-time-playback-started': 'onRealTimePlaybackStarted' diff --git a/app/views/play/level/LevelGoalsView.coffee b/app/views/play/level/LevelGoalsView.coffee index 4a187879d..27849d659 100644 --- a/app/views/play/level/LevelGoalsView.coffee +++ b/app/views/play/level/LevelGoalsView.coffee @@ -11,6 +11,9 @@ stateIconMap = module.exports = class LevelGoalsView extends CocoView id: 'goals-view' template: template + className: 'secret expanded' + playbackEnded: false + mouseEntered: false subscriptions: 'goal-manager:new-goal-states': 'onNewGoalStates' @@ -67,6 +70,7 @@ module.exports = class LevelGoalsView extends CocoView Backbone.Mediator.publish 'audio-player:play-sound', trigger: 'goal-incomplete-again', volume: 1 @previousGoalStatus[goal.id] = state.status @$el.removeClass('secret') if goals.length > 0 + @updatePlacement() onSurfacePlaybackRestarted: -> @playbackEnded = false @@ -78,14 +82,6 @@ module.exports = class LevelGoalsView extends CocoView @$el.addClass 'brighter' @updatePlacement() - render: -> - super() - @$el.addClass('secret').addClass('expanded') - - afterRender: -> - super() - @updatePlacement() - updatePlacement: -> expand = @playbackEnded or @mouseEntered return if expand is @expanded diff --git a/app/views/play/level/LevelLoadingView.coffee b/app/views/play/level/LevelLoadingView.coffee index 62834d214..73aa77011 100644 --- a/app/views/play/level/LevelLoadingView.coffee +++ b/app/views/play/level/LevelLoadingView.coffee @@ -1,5 +1,6 @@ CocoView = require 'views/kinds/CocoView' template = require 'templates/play/level/level_loading' +utils = require 'lib/utils' module.exports = class LevelLoadingView extends CocoView id: 'level-loading-view' @@ -22,9 +23,17 @@ module.exports = class LevelLoadingView extends CocoView onLevelLoaded: (e) -> @level = e.level - goalList = @$el.find('.level-loading-goals').removeClass('secret').find('ul') + goalContainer = @$el.find('.level-loading-goals') + goalList = goalContainer.find('ul') + goalCount = 0 for goalID, goal of @level.get('goals') when (not goal.team or goal.team is e.team) and not goal.hiddenGoal - goalList.append $('
  • ' + goal.name + '
  • ') + name = utils.i18n goal, 'name' + goalList.append $('
  • ' + name + '
  • ') + ++goalCount + if goalCount + goalContainer.removeClass('secret') + if goalCount is 1 + goalContainer.find('.panel-heading').text $.i18n.t 'play_level.goal' # Not plural showReady: -> return if @shownReady diff --git a/app/views/play/level/PlayLevelView.coffee b/app/views/play/level/PlayLevelView.coffee index f62519c31..73d6036f5 100644 --- a/app/views/play/level/PlayLevelView.coffee +++ b/app/views/play/level/PlayLevelView.coffee @@ -351,6 +351,17 @@ module.exports = class PlayLevelView extends RootView console.debug "Level unveiled after #{(loadDuration / 1000).toFixed(2)}s" application.tracker?.trackEvent 'Finished Level Load', level: @levelID, label: @levelID, loadDuration: loadDuration, ['Google Analytics'] application.tracker?.trackTiming loadDuration, 'Level Load Time', @levelID, @levelID + @playAmbientSound() + + playAmbientSound: -> + return if @ambientSound + return unless file = {Dungeon: 'ambient-dungeon', Grass: 'ambient-grass'}[@level.get('terrain')] + src = "/file/interface/#{file}#{AudioPlayer.ext}" + unless AudioPlayer.getStatus(src)?.loaded + AudioPlayer.preloadSound src + Backbone.Mediator.subscribeOnce 'audio-player:loaded', @playAmbientSound, @ + return + @ambientSound = createjs.Sound.play src, loop: -1 restoreSessionState: -> return if @alreadyLoadedState @@ -360,6 +371,7 @@ module.exports = class PlayLevelView extends RootView Backbone.Mediator.publish 'level:set-time', time: 0, frameOffset: state.frame if @level.get('type', true) is 'hero' Backbone.Mediator.publish 'tome:select-primary-sprite', {} + @surface.focusOnHero() else if state.selected # TODO: Should also restore selected spell here by saving spellName Backbone.Mediator.publish 'level:select-sprite', thangID: state.selected, spellName: null @@ -601,6 +613,7 @@ module.exports = class PlayLevelView extends RootView @god?.destroy() @goalManager?.destroy() @scriptManager?.destroy() + @ambientSound.stop() $(window).off 'resize', @onWindowResize delete window.world # not sure where this is set, but this is one way to clean it up clearInterval(@pointerInterval) diff --git a/app/views/play/level/tome/ThangListView.coffee b/app/views/play/level/tome/ThangListView.coffee index 7f69d55d0..f8b0cc9d0 100644 --- a/app/views/play/level/tome/ThangListView.coffee +++ b/app/views/play/level/tome/ThangListView.coffee @@ -11,9 +11,6 @@ module.exports = class ThangListView extends CocoView id: 'thang-list-view' template: template - subscriptions: - 'tome:select-primary-sprite': 'onSelectPrimarySprite' - constructor: (options) -> super options @spells = options.spells @@ -75,6 +72,9 @@ module.exports = class ThangListView extends CocoView return entry.spells[0] null + selectPrimarySprite: -> + @entries[0]?.select() + adjustThangs: (spells, thangs) -> # TODO: it would be nice to not have to do this any more, like if we migrate to the hero levels. # Recreating all the ThangListEntryViews and their ThangAvatarViews is pretty slow. @@ -90,9 +90,6 @@ module.exports = class ThangListView extends CocoView @sortThangs() @addThangListEntries() - onSelectPrimarySprite: (e) -> - @entries[0]?.select() - destroy: -> entry.destroy() for entry in @entries super() diff --git a/app/views/play/level/tome/TomeView.coffee b/app/views/play/level/tome/TomeView.coffee index c473262af..fb6fca2e9 100644 --- a/app/views/play/level/tome/TomeView.coffee +++ b/app/views/play/level/tome/TomeView.coffee @@ -52,6 +52,7 @@ module.exports = class TomeView extends CocoView 'surface:sprite-selected': 'onSpriteSelected' 'god:new-world-created': 'onNewWorld' 'tome:comment-my-code': 'onCommentMyCode' + 'tome:select-primary-sprite': 'onSelectPrimarySprite' events: 'click #spell-view': 'onSpellViewClick' @@ -63,7 +64,7 @@ module.exports = class TomeView extends CocoView programmableThangs = _.filter @options.thangs, 'isProgrammable' @createSpells programmableThangs, programmableThangs[0]?.world # Do before spellList, thangList, and castButton @spellList = @insertSubView new SpellListView spells: @spells, supermodel: @supermodel - @thangList = @insertSubView new ThangListView spells: @spells, thangs: @options.thangs, supermodel: @supermodel + @thangList = @insertSubView new ThangListView spells: @spells, thangs: @options.thangs, supermodel: @supermodel unless @options.level.get('type', true) is 'hero' @castButton = @insertSubView new CastButtonView spells: @spells, levelID: @options.levelID @teamSpellMap = @generateTeamSpellMap(@spells) unless programmableThangs.length @@ -77,7 +78,7 @@ module.exports = class TomeView extends CocoView thangs = _.filter e.world.thangs, 'inThangList' programmableThangs = _.filter thangs, 'isProgrammable' @createSpells programmableThangs, e.world - @thangList.adjustThangs @spells, thangs + @thangList?.adjustThangs @spells, thangs @spellList.adjustSpells @spells onCommentMyCode: (e) -> @@ -200,7 +201,7 @@ module.exports = class TomeView extends CocoView @$el.find('#' + @spellView.id).after(@spellView.el).remove() @$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove() @castButton.attachTo @spellView - @thangList.$el.hide() + @thangList?.$el.hide() Backbone.Mediator.publish 'tome:spell-shown', thang: thang, spell: spell @spellList.setThangAndSpell thang, spell @spellView?.setThang thang @@ -218,9 +219,11 @@ module.exports = class TomeView extends CocoView selectedThangSpells = (@spells[spellKey] for spellKey in @thangSpells[thang.id]) if spellName spell = _.find selectedThangSpells, {name: spellName} - else + else if @thangList spell = @thangList.topSpellForThang thang #spell = selectedThangSpells[0] # TODO: remember last selected spell for this thang + else + spell = _.find selectedThangSpells, (spell) -> true # Just grab one spell reloadAllCode: -> @@ -231,6 +234,12 @@ module.exports = class TomeView extends CocoView spell.updateLanguageAether e.language for spellKey, spell of @spells when spell.canWrite() @cast() + onSelectPrimarySprite: (e) -> + if @thangList + @thangList.selectPrimarySprite() + else + Backbone.Mediator.publish 'level:select-sprite', thangID: 'Hero Placeholder' + destroy: -> spell.destroy() for spellKey, spell of @spells @worker?.terminate() diff --git a/server/levels/level_handler.coffee b/server/levels/level_handler.coffee index e2d10747f..74cb8013b 100644 --- a/server/levels/level_handler.coffee +++ b/server/levels/level_handler.coffee @@ -27,6 +27,7 @@ LevelHandler = class LevelHandler extends Handler 'showsGuide' 'banner' 'employerDescription' + 'terrain' ] postEditableProperties: ['name']