diff --git a/app/assets/javascripts/workers/worker_world.js b/app/assets/javascripts/workers/worker_world.js index c7a6ee560..2ec1d73f8 100644 --- a/app/assets/javascripts/workers/worker_world.js +++ b/app/assets/javascripts/workers/worker_world.js @@ -464,6 +464,11 @@ self.addFlagEvent = function addFlagEvent(flagEvent) { self.world.addFlagEvent(flagEvent); }; +self.stopRealTimePlayback = function stopRealTimePlayback() { + if(!self.world) return; + self.world.realTime = false; +}; + self.addEventListener('message', function(event) { self[event.data.func](event.data.args); }); diff --git a/app/lib/Angel.coffee b/app/lib/Angel.coffee index 59547ca16..3e7a8efce 100644 --- a/app/lib/Angel.coffee +++ b/app/lib/Angel.coffee @@ -14,6 +14,7 @@ module.exports = class Angel extends CocoClass subscriptions: 'level:flag-updated': 'onFlagEvent' + 'playback:stop-real-time-playback': 'onStopRealTimePlayback' constructor: (@shared) -> super() @@ -212,6 +213,10 @@ module.exports = class Angel extends CocoClass return unless @running and @work.realTime @worker.postMessage func: 'addFlagEvent', args: e + onStopRealTimePlayback: (e) -> + return unless @running and @work.realTime + @work.realTime = false + @worker.postMessage func: 'stopRealTimePlayback' #### Synchronous code for running worlds on main thread (profiling / IE9) #### simulateSync: (work) => diff --git a/app/lib/God.coffee b/app/lib/God.coffee index de1f6b685..8d1b90228 100644 --- a/app/lib/God.coffee +++ b/app/lib/God.coffee @@ -65,7 +65,7 @@ module.exports = class God extends CocoClass isPreloading = angel.running and angel.work.preload and _.isEqual angel.work.userCodeMap, userCodeMap, (a, b) -> return a.raw is b.raw if a?.raw? and b?.raw? undefined # Let default equality test suffice. - if not hadPreloader and isPreloading + if not hadPreloader and isPreloading and not realTime angel.finalizePreload() hadPreloader = true else if preload and angel.running and not angel.work.preload diff --git a/app/lib/surface/Surface.coffee b/app/lib/surface/Surface.coffee index 92715bd5a..013c6801f 100644 --- a/app/lib/surface/Surface.coffee +++ b/app/lib/surface/Surface.coffee @@ -8,6 +8,7 @@ CameraBorder = require './CameraBorder' Layer = require './Layer' Letterbox = require './Letterbox' Dimmer = require './Dimmer' +CountdownScreen = require './CountdownScreen' PlaybackOverScreen = require './PlaybackOverScreen' DebugDisplay = require './DebugDisplay' CoordinateDisplay = require './CoordinateDisplay' @@ -100,6 +101,7 @@ module.exports = Surface = class Surface extends CocoClass @spriteBoss.destroy() @chooser?.destroy() @dimmer?.destroy() + @countdownScreen?.destroy() @playbackOverScreen?.destroy() @stage.clear() @musicPlayer?.destroy() @@ -402,6 +404,7 @@ module.exports = Surface = class Surface extends CocoClass @surfaceLayer.addChild @cameraBorder = new CameraBorder bounds: @camera.bounds @screenLayer.addChild new Letterbox canvasWidth: canvasWidth, canvasHeight: canvasHeight @spriteBoss = new SpriteBoss camera: @camera, surfaceLayer: @surfaceLayer, surfaceTextLayer: @surfaceTextLayer, world: @world, thangTypes: @options.thangTypes, choosing: @options.choosing, navigateToSelection: @options.navigateToSelection, showInvisible: @options.showInvisible + @countdownScreen ?= new CountdownScreen camera: @camera, layer: @screenLayer @playbackOverScreen ?= new PlaybackOverScreen camera: @camera, layer: @screenLayer @stage.enableMouseOver(10) @stage.addEventListener 'stagemousemove', @onMouseMove @@ -414,6 +417,7 @@ module.exports = Surface = class Surface extends CocoClass @onResize() onResize: (e) => + return if @destroyed oldWidth = parseInt @canvas.attr('width'), 10 oldHeight = parseInt @canvas.attr('height'), 10 aspectRatio = oldWidth / oldHeight @@ -630,6 +634,7 @@ module.exports = Surface = class Surface extends CocoClass @realTime = true @onResize() @spriteBoss.selfWizardSprite?.toggle false + @playing = false # Will start when countdown is done. onRealTimePlaybackEnded: (e) -> @realTime = false diff --git a/app/lib/world/world.coffee b/app/lib/world/world.coffee index e76ab2707..629a25f63 100644 --- a/app/lib/world/world.coffee +++ b/app/lib/world/world.coffee @@ -15,6 +15,7 @@ DESERIALIZATION_INTERVAL = 10 REAL_TIME_BUFFER_MIN = 2 * PROGRESS_UPDATE_INTERVAL REAL_TIME_BUFFER_MAX = 3 * PROGRESS_UPDATE_INTERVAL REAL_TIME_BUFFERED_WAIT_INTERVAL = 0.5 * PROGRESS_UPDATE_INTERVAL +REAL_TIME_COUNTDOWN_DELAY = 3000 # match CountdownScreen ITEM_ORIGINAL = '53e12043b82921000051cdf9' module.exports = class World @@ -93,12 +94,14 @@ module.exports = class World loadFrames: (loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) -> return if @aborted console.log 'Warning: loadFrames called on empty World (no thangs).' unless @thangs.length + continueLaterFn = => + @loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed + if @realTime and not @countdownFinished + return setTimeout @finishCountdown(continueLaterFn), REAL_TIME_COUNTDOWN_DELAY t1 = now() @t0 ?= t1 @worldLoadStartTime ?= t1 @lastRealTimeUpdate ?= 0 - continueLaterFn = => - @loadFrames(loadedCallback, errorCallback, loadProgressCallback, skipDeferredLoading, loadUntilFrame) unless @destroyed frameToLoadUntil = if loadUntilFrame then loadUntilFrame + 1 else @totalFrames # Might stop early if debugging. i = @frames.length while i < frameToLoadUntil and i < @totalFrames @@ -123,6 +126,11 @@ module.exports = class World loadProgressCallback? 1 loadedCallback() + finishCountdown: (continueLaterFn) -> => + return if @destroyed + @countdownFinished = true + continueLaterFn() + shouldDelayRealTimeSimulation: (t) -> return false unless @realTime timeSinceStart = t - @worldLoadStartTime diff --git a/app/locale/en.coffee b/app/locale/en.coffee index 27c340771..a6f87da93 100644 --- a/app/locale/en.coffee +++ b/app/locale/en.coffee @@ -358,6 +358,7 @@ grid: "Grid" customize_wizard: "Customize Wizard" home: "Home" + stop: "Stop" game_menu: "Game Menu" guide: "Guide" restart: "Restart" diff --git a/app/schemas/subscriptions/play.coffee b/app/schemas/subscriptions/play.coffee index f9dce2648..c73370473 100644 --- a/app/schemas/subscriptions/play.coffee +++ b/app/schemas/subscriptions/play.coffee @@ -147,6 +147,10 @@ module.exports = 'playback:manually-scrubbed': {} # TODO schema + 'playback:stop-real-time-playback': + type: 'object' + additionalProperties: false + 'playback:real-time-playback-started': type: 'object' additionalProperties: false diff --git a/app/styles/play/level.sass b/app/styles/play/level.sass index d2fa31a62..524b01db8 100644 --- a/app/styles/play/level.sass +++ b/app/styles/play/level.sass @@ -19,6 +19,10 @@ body.is-playing margin: 0 auto #control-bar-view width: 100% + button, h4 + display: none + #stop-real-time-playback-button + display: block #playback-view $flags-width: 200px width: 90% diff --git a/app/styles/play/level/control_bar.sass b/app/styles/play/level/control_bar.sass index ad5c89a36..d8828f4c6 100644 --- a/app/styles/play/level/control_bar.sass +++ b/app/styles/play/level/control_bar.sass @@ -41,5 +41,5 @@ height: 24px - #level-done-button + #level-done-button, #stop-real-time-playback-button display: none diff --git a/app/templates/play/level/control_bar.jade b/app/templates/play/level/control_bar.jade index 436c177a0..d1d2eeda8 100644 --- a/app/templates/play/level/control_bar.jade +++ b/app/templates/play/level/control_bar.jade @@ -9,6 +9,9 @@ h4.title | - a(href=editorLink, data-i18n="nav.editor", title="Open " + worldName + " in the Level Editor") Editor + +button.btn.btn-xs.btn-warning.banner#stop-real-time-playback-button(title="Stop real-time playback", data-i18n="play_level.stop") Stop + button.btn.btn-xs.btn-inverse.banner#game-menu-button(title="Show game menu", data-i18n="play_level.game_menu") Game Menu button.btn.btn-xs.btn-success.banner#docs-button(title="Show level instructions", data-i18n="play_level.guide") Guide diff --git a/app/views/play/level/ControlBarView.coffee b/app/views/play/level/ControlBarView.coffee index 1d07c745c..d4fbe91c6 100644 --- a/app/views/play/level/ControlBarView.coffee +++ b/app/views/play/level/ControlBarView.coffee @@ -16,13 +16,13 @@ module.exports = class ControlBarView extends CocoView window.tracker?.trackEvent 'Clicked Docs', level: @level.get('name'), label: @level.get('name') @showGuideModal() - 'click #next-game-button': -> - Backbone.Mediator.publish 'next-game-pressed' + 'click #next-game-button': -> Backbone.Mediator.publish 'next-game-pressed', {} - 'click #game-menu-button': -> - @showGameMenuModal() + 'click #game-menu-button': 'showGameMenuModal' - 'click': -> Backbone.Mediator.publish 'tome:focus-editor' + 'click #stop-real-time-playback-button': -> Backbone.Mediator.publish 'playback:stop-real-time-playback', {} + + 'click': -> Backbone.Mediator.publish 'tome:focus-editor', {} constructor: (options) -> @worldName = options.worldName diff --git a/app/views/play/level/LevelPlaybackView.coffee b/app/views/play/level/LevelPlaybackView.coffee index 63fdc9379..b783d3db4 100644 --- a/app/views/play/level/LevelPlaybackView.coffee +++ b/app/views/play/level/LevelPlaybackView.coffee @@ -26,6 +26,7 @@ module.exports = class LevelPlaybackView extends CocoView 'level-set-letterbox': 'onSetLetterbox' 'tome:cast-spells': 'onTomeCast' 'playback:real-time-playback-ended': 'onRealTimePlaybackEnded' + 'playback:stop-real-time-playback': 'onStopRealTimePlayback' events: 'click #debug-toggle': 'onToggleDebug' @@ -34,11 +35,11 @@ module.exports = class LevelPlaybackView extends CocoView 'click #edit-editor-config': 'onEditEditorConfig' 'click #view-keyboard-shortcuts': 'onViewKeyboardShortcuts' 'click #music-button': 'onToggleMusic' - 'click #zoom-in-button': -> Backbone.Mediator.publish('camera-zoom-in') unless @shouldIgnore() - 'click #zoom-out-button': -> Backbone.Mediator.publish('camera-zoom-out') unless @shouldIgnore() + 'click #zoom-in-button': -> Backbone.Mediator.publish('camera-zoom-in', {}) unless @shouldIgnore() + 'click #zoom-out-button': -> Backbone.Mediator.publish('camera-zoom-out', {}) unless @shouldIgnore() 'click #volume-button': 'onToggleVolume' 'click #play-button': 'onTogglePlay' - 'click': -> Backbone.Mediator.publish 'tome:focus-editor' unless @realTime + 'click': -> Backbone.Mediator.publish 'tome:focus-editor', {} unless @realTime 'mouseenter #timeProgress': 'onProgressEnter' 'mouseleave #timeProgress': 'onProgressLeave' 'mousemove #timeProgress': 'onProgressHover' @@ -308,6 +309,9 @@ module.exports = class LevelPlaybackView extends CocoView @realTime = false @togglePlaybackControls true + onStopRealTimePlayback: (e) -> + Backbone.Mediator.publish 'playback:real-time-playback-ended', {} + onSetDebug: (e) -> flag = $('#debug-toggle i.icon-ok') flag.toggleClass 'invisible', not e.debug