2014-01-03 13:32:13 -05:00
CocoClass = require ' lib/CocoClass '
path = require ' ./path '
Dropper = require ' ./Dropper '
AudioPlayer = require ' lib/AudioPlayer '
{ me } = require ' lib/auth '
Camera = require ' ./Camera '
CameraBorder = require ' ./CameraBorder '
Layer = require ' ./Layer '
Letterbox = require ' ./Letterbox '
Dimmer = require ' ./Dimmer '
2014-08-26 01:05:24 -04:00
CountdownScreen = require ' ./CountdownScreen '
2014-02-25 20:14:39 -05:00
PlaybackOverScreen = require ' ./PlaybackOverScreen '
2014-08-30 00:46:26 -04:00
WaitingScreen = require ' ./WaitingScreen '
2014-01-03 13:32:13 -05:00
DebugDisplay = require ' ./DebugDisplay '
CoordinateDisplay = require ' ./CoordinateDisplay '
2014-08-26 16:56:57 -04:00
CoordinateGrid = require ' ./CoordinateGrid '
2014-01-03 13:32:13 -05:00
SpriteBoss = require ' ./SpriteBoss '
PointChooser = require ' ./PointChooser '
RegionChooser = require ' ./RegionChooser '
MusicPlayer = require ' ./MusicPlayer '
module.exports = Surface = class Surface extends CocoClass
stage: null
layers: null
surfaceLayer: null
surfaceTextLayer: null
screenLayer: null
2014-08-26 16:56:57 -04:00
gridLayer: null
2014-01-03 13:32:13 -05:00
spriteBoss: null
debugDisplay: null
currentFrame: 0
lastFrame: null
totalFramesDrawn: 0
2014-09-22 01:10:52 -04:00
playing: false # play vs. pause -- match default button state in playback.jade
2014-01-03 13:32:13 -05:00
dead: false # if we kill it for some reason
imagesLoaded: false
worldLoaded: false
scrubbing: false
debug: false
defaults:
wizards: true
paths: true
grid: false
navigateToSelection: true
choosing: false # 'point', 'region', 'ratio-region'
2014-09-23 21:59:08 -04:00
coords: null # use world defaults, or set to false/true to override
2014-01-03 13:32:13 -05:00
playJingle: false
showInvisible: false
2014-04-28 19:41:18 -04:00
frameRate: 30 # Best as a divisor of 60, like 15, 30, 60, with RAF_SYNCHED timing.
2014-01-03 13:32:13 -05:00
subscriptions:
2014-08-27 15:24:03 -04:00
' level:disable-controls ' : ' onDisableControls '
' level:enable-controls ' : ' onEnableControls '
' level:set-playing ' : ' onSetPlaying '
' level:set-debug ' : ' onSetDebug '
' level:toggle-debug ' : ' onToggleDebug '
' level:toggle-pathfinding ' : ' onTogglePathFinding '
' level:set-time ' : ' onSetTime '
' camera:set-camera ' : ' onSetCamera '
2014-01-03 13:32:13 -05:00
' level:restarted ' : ' onLevelRestarted '
' god:new-world-created ' : ' onNewWorld '
2014-08-21 19:27:52 -04:00
' god:streaming-world-updated ' : ' onNewWorld '
2014-01-03 13:32:13 -05:00
' tome:cast-spells ' : ' onCastSpells '
2014-08-27 15:24:03 -04:00
' level:set-letterbox ' : ' onSetLetterbox '
2014-05-09 18:07:30 -04:00
' application:idle-changed ' : ' onIdleChanged '
2014-05-12 18:54:07 -04:00
' camera:zoom-updated ' : ' onZoomUpdated '
2014-08-30 00:46:26 -04:00
' playback:real-time-playback-waiting ' : ' onRealTimePlaybackWaiting '
2014-08-23 16:54:52 -04:00
' playback:real-time-playback-started ' : ' onRealTimePlaybackStarted '
' playback:real-time-playback-ended ' : ' onRealTimePlaybackEnded '
2014-08-24 19:09:06 -04:00
' level:flag-color-selected ' : ' onFlagColorSelected '
2014-01-03 13:32:13 -05:00
shortcuts:
2014-01-31 19:32:46 -05:00
' ctrl+ \\ , ⌘+ \\ ' : ' onToggleDebug '
' ctrl+o, ⌘+o ' : ' onTogglePathFinding '
2014-01-03 13:32:13 -05:00
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
#- Initialization
2014-01-03 13:32:13 -05:00
constructor: (@world, @canvas, givenOptions) ->
super ( )
@layers = [ ]
@options = _ . clone ( @ defaults )
@options = _ . extend ( @ options , givenOptions ) if givenOptions
@ initEasel ( )
@ initAudio ( )
2014-09-03 21:35:14 -04:00
@onResize = _ . debounce @ onResize , 500 # At least as much as $level-resize-transition-time.
2014-05-12 16:28:46 -04:00
$ ( window ) . on ' resize ' , @ onResize
2014-05-14 13:35:16 -04:00
if @ world . ended
_ . defer => @ setWorld @ world
2014-01-03 13:32:13 -05:00
2014-09-05 12:20:29 -04:00
initEasel: ->
@stage = new createjs . Stage ( @ canvas [ 0 ] ) # Takes DOM objects, not jQuery objects.
canvasWidth = parseInt @ canvas . attr ( ' width ' ) , 10
canvasHeight = parseInt @ canvas . attr ( ' height ' ) , 10
@camera = AudioPlayer.camera = new Camera @ canvas
@ layers . push @surfaceLayer = new Layer name: ' Surface ' , layerPriority: 0 , transform: Layer . TRANSFORM_SURFACE , camera: @ camera
@ layers . push @surfaceTextLayer = new Layer name: ' Surface Text ' , layerPriority: 1 , transform: Layer . TRANSFORM_SURFACE_TEXT , camera: @ camera
@ layers . push @gridLayer = new Layer name: ' Grid ' , layerPriority: 2 , transform: Layer . TRANSFORM_SURFACE , camera: @ camera
@ layers . push @screenLayer = new Layer name: ' Screen ' , layerPriority: 3 , transform: Layer . TRANSFORM_SCREEN , camera: @ camera
@ stage . addChild @ layers . . .
@ 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
@waitingScreen = new WaitingScreen camera: @ camera , layer: @ screenLayer
@ initCoordinates ( )
@ stage . enableMouseOver ( 10 )
@ stage . addEventListener ' stagemousemove ' , @ onMouseMove
@ stage . addEventListener ' stagemousedown ' , @ onMouseDown
@ canvas [ 0 ] . addEventListener ' mouseup ' , @ onMouseUp
@ canvas . on ' mousewheel ' , @ onMouseWheel
@ hookUpChooseControls ( ) if @ options . choosing
createjs.Ticker.timingMode = createjs . Ticker . RAF_SYNCHED
createjs . Ticker . setFPS @ options . frameRate
@ onResize ( )
initCoordinates: ->
@ coordinateGrid ? = new CoordinateGrid { camera: @ camera , layer: @ gridLayer , textLayer: @ surfaceTextLayer } , @ world . size ( )
@ coordinateGrid . showGrid ( ) if @ world . showGrid or @ options . grid
2014-09-23 21:39:52 -04:00
showCoordinates = if @ options . coords ? then @ options . coords else @ world . showCoordinates
@ coordinateDisplay ? = new CoordinateDisplay camera: @ camera , layer: @ surfaceTextLayer if showCoordinates
2014-09-05 12:20:29 -04:00
hookUpChooseControls: ->
chooserOptions = stage: @ stage , surfaceLayer: @ surfaceLayer , camera: @ camera , restrictRatio: @ options . choosing is ' ratio-region '
klass = if @ options . choosing is ' point ' then PointChooser else RegionChooser
@chooser = new klass chooserOptions
initAudio: ->
@musicPlayer = new MusicPlayer ( )
2014-02-12 15:41:41 -05:00
2014-09-05 12:20:29 -04:00
#- Setting the world
2014-09-06 22:50:31 -04:00
2014-01-03 13:32:13 -05:00
setWorld: (@world) ->
@worldLoaded = true
2014-08-22 20:11:40 -04:00
lastFrame = Math . min ( @ getCurrentFrame ( ) , @ world . frames . length - 1 )
2014-07-17 18:50:29 -04:00
@ world . getFrame ( lastFrame ) . restoreState ( ) unless @ options . choosing
2014-01-03 13:32:13 -05:00
@spriteBoss.world = @ world
@ showLevel ( )
@ updateState true if @ loaded
@ onFrameChanged ( )
2014-09-05 12:20:29 -04:00
showLevel: ->
return if @ destroyed
return if @ loaded
@loaded = true
@ spriteBoss . createMarks ( )
@ spriteBoss . createIndieSprites @ world . indieSprites , @ options . wizards
@ updateState true
@ drawCurrentFrame ( )
createjs . Ticker . addEventListener ' tick ' , @ tick
Backbone . Mediator . publish ' level:started ' , { }
2014-01-31 13:21:32 -05:00
2014-09-05 12:20:29 -04:00
createOpponentWizard: (opponent) ->
@ spriteBoss . createOpponentWizard opponent
2014-01-31 13:21:32 -05:00
2014-09-05 12:20:29 -04:00
#- Update loop
2014-01-31 13:21:32 -05:00
2014-09-05 12:20:29 -04:00
tick: (e) =>
# seems to be a bug where only one object can register with the Ticker...
oldFrame = @ currentFrame
oldWorldFrame = Math . floor oldFrame
lastFrame = @ world . frames . length - 1
while true
Dropper . tick ( )
@ trailmaster . tick ( ) if @ trailmaster
# Skip some frame updates unless we're playing and not at end (or we haven't drawn much yet)
frameAdvanced = ( @ playing and @ currentFrame < lastFrame ) or @ totalFramesDrawn < 2
if frameAdvanced and @ playing
advanceBy = @ world . frameRate / @ options . frameRate
if @ fastForwardingToFrame and @ currentFrame < @ fastForwardingToFrame - advanceBy
advanceBy = Math . min ( @ currentFrame + advanceBy * @ fastForwardingSpeed , @ fastForwardingToFrame ) - @ currentFrame
else if @ fastForwardingToFrame
@fastForwardingToFrame = @fastForwardingSpeed = null
@ currentFrame += advanceBy
@currentFrame = Math . min @ currentFrame , lastFrame
newWorldFrame = Math . floor @ currentFrame
worldFrameAdvanced = newWorldFrame isnt oldWorldFrame
if worldFrameAdvanced
# Only restore world state when it will correspond to an integer WorldFrame, not interpolated frame.
@ restoreWorldState ( )
oldWorldFrame = newWorldFrame
break unless Dropper . drop ( )
if frameAdvanced and not worldFrameAdvanced
# We didn't end the above loop on an integer frame, so do the world state update.
@ restoreWorldState ( )
2014-01-20 15:57:45 -05:00
2014-09-05 12:20:29 -04:00
# these are skipped for dropped frames
@ updateState @ currentFrame isnt oldFrame
@ drawCurrentFrame e
@ onFrameChanged ( )
@ updatePaths ( ) if ( @ totalFramesDrawn % 4 ) is 0 or createjs . Ticker . getMeasuredFPS ( ) > createjs . Ticker . getFPS ( ) - 5
Backbone . Mediator . publish ( ' surface:ticked ' , { dt: 1 / @ options . frameRate } )
mib = @ stage . mouseInBounds
if @ mouseInBounds isnt mib
Backbone . Mediator . publish ( ' surface:mouse- ' + ( if mib then ' over ' else ' out ' ) , { } )
@mouseInBounds = mib
2014-09-21 23:49:45 -04:00
@mouseIsDown = false
2014-01-20 15:57:45 -05:00
2014-09-05 12:20:29 -04:00
restoreWorldState: ->
frame = @ world . getFrame ( @ getCurrentFrame ( ) )
frame . restoreState ( )
current = Math . max ( 0 , Math . min ( @ currentFrame , @ world . frames . length - 1 ) )
if current - Math . floor ( current ) > 0.01 and Math . ceil ( current ) < @ world . frames . length - 1
next = Math . ceil current
ratio = current % 1
@ world . frames [ next ] . restorePartialState ratio if next > 1
frame . clearEvents ( ) if parseInt ( @ currentFrame ) is parseInt ( @ lastFrame )
@ spriteBoss . updateSounds ( ) if parseInt ( @ currentFrame ) isnt parseInt ( @ lastFrame )
updateState: (frameChanged) ->
# world state must have been restored in @restoreWorldState
2014-09-21 23:49:45 -04:00
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
2014-09-05 12:20:29 -04:00
@ camera . updateZoom ( )
@ spriteBoss . update frameChanged
@ dimmer ? . setSprites @ spriteBoss . sprites
2014-01-20 15:57:45 -05:00
2014-09-05 12:20:29 -04:00
drawCurrentFrame: (e) ->
++ @ totalFramesDrawn
@ stage . update e
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
#- Setting play/pause and progress
2014-09-06 22:50:31 -04:00
2014-01-03 13:32:13 -05:00
setProgress: (progress, scrubDuration=500) ->
2014-02-25 20:14:39 -05:00
progress = Math . max ( Math . min ( progress , 1 ) , 0.0 )
2014-01-03 13:32:13 -05:00
2014-08-23 12:52:05 -04:00
@fastForwardingToFrame = null
2014-01-03 13:32:13 -05:00
@scrubbing = true
onTweenEnd = =>
@scrubbingTo = null
@scrubbing = false
@scrubbingPlaybackSpeed = null
if @ scrubbingTo ?
# cut to the chase for existing tween
createjs . Tween . removeTweens ( @ )
@currentFrame = @ scrubbingTo
2014-09-25 00:01:58 -04:00
@scrubbingTo = Math . min ( Math . round ( progress * ( @ world . frames . length - 1 ) ) , @ world . frames . length - 1 )
2014-01-03 13:32:13 -05:00
@scrubbingPlaybackSpeed = Math . sqrt ( Math . abs ( @ scrubbingTo - @ currentFrame ) * @ world . dt / ( scrubDuration or 0.5 ) )
if scrubDuration
t = createjs . Tween
. get ( @ )
2014-08-23 12:52:05 -04:00
. to ( { currentFrame: @ scrubbingTo } , scrubDuration , createjs . Ease . sineInOut )
2014-01-03 13:32:13 -05:00
. call ( onTweenEnd )
2014-02-28 14:27:32 -05:00
t . addEventListener ( ' change ' , @ onFramesScrubbed )
2014-01-03 13:32:13 -05:00
else
@currentFrame = @ scrubbingTo
2014-02-28 14:27:32 -05:00
@ onFramesScrubbed ( ) # For performance, don't play these for instant transitions.
2014-01-03 13:32:13 -05:00
onTweenEnd ( )
2014-03-12 12:10:36 -04:00
return unless @ loaded
2014-01-03 13:32:13 -05:00
@ updateState true
@ onFrameChanged ( )
2014-02-28 14:27:32 -05:00
onFramesScrubbed: (e) =>
2014-03-12 12:10:36 -04:00
return unless @ loaded
2014-02-28 14:27:32 -05:00
if e
# Gotta play all the sounds when scrubbing (but not when doing an immediate transition).
rising = @ currentFrame > @ lastFrame
actualCurrentFrame = @ currentFrame
tempFrame = if rising then Math . ceil ( @ lastFrame ) else Math . floor ( @ lastFrame )
while true # temporary fix to stop cacophony
break if rising and tempFrame > actualCurrentFrame
break if ( not rising ) and tempFrame < actualCurrentFrame
@currentFrame = tempFrame
frame = @ world . getFrame ( @ getCurrentFrame ( ) )
frame . restoreState ( )
2014-04-28 19:31:51 -04:00
volume = Math . max ( 0.05 , Math . min ( 1 , 1 / @ scrubbingPlaybackSpeed ) )
sprite . playSounds false , volume for sprite in @ spriteBoss . spriteArray
2014-02-28 14:27:32 -05:00
tempFrame += if rising then 1 else - 1
@currentFrame = actualCurrentFrame
@ restoreWorldState ( )
2014-02-24 20:01:36 -05:00
@ spriteBoss . update true
2014-01-03 13:32:13 -05:00
@ onFrameChanged ( )
getCurrentFrame: ->
2014-07-17 18:50:29 -04:00
return Math . max ( 0 , Math . min ( Math . floor ( @ currentFrame ) , @ world . frames . length - 1 ) )
2014-01-03 13:32:13 -05:00
2014-09-05 12:20:29 -04:00
setPaused: (paused) ->
# We want to be able to essentially stop rendering the surface if it doesn't need to animate anything.
# If pausing, though, we want to give it enough time to finish any tweens.
performToggle = =>
createjs . Ticker . setFPS if paused then 1 else @ options . frameRate
@surfacePauseTimeout = null
clearTimeout @ surfacePauseTimeout if @ surfacePauseTimeout
clearTimeout @ surfaceZoomPauseTimeout if @ surfaceZoomPauseTimeout
@surfacePauseTimeout = @surfaceZoomPauseTimeout = null
if paused
@surfacePauseTimeout = _ . delay performToggle , 2000
@ spriteBoss . stop ( )
@ playbackOverScreen . show ( )
else
performToggle ( )
@ spriteBoss . play ( )
@ playbackOverScreen . hide ( )
#- Changes and events that only need to happen when the frame has changed
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
onFrameChanged: (force) ->
2014-09-25 01:07:55 -04:00
@currentFrame = Math . min ( @ currentFrame , @ world . frames . length - 1 )
2014-09-05 12:20:29 -04:00
@ debugDisplay ? . updateFrame @ currentFrame
return if @ currentFrame is @ lastFrame and not force
progress = @ getProgress ( )
Backbone . Mediator . publish ( ' surface:frame-changed ' ,
selectedThang: @ spriteBoss . selectedSprite ? . thang
progress: progress
frame: @ currentFrame
world: @ world
)
if @ lastFrame < @ world . frames . length and @ currentFrame >= @ world . totalFrames - 1
@ended = true
@ setPaused true
Backbone . Mediator . publish ' surface:playback-ended ' , { }
else if @ currentFrame < @ world . totalFrames and @ ended
@ended = false
@ setPaused false
Backbone . Mediator . publish ' surface:playback-restarted ' , { }
@lastFrame = @ currentFrame
2014-09-24 23:25:30 -04:00
getProgress: -> @ currentFrame / Math . max ( 1 , @ world . frames . length - 1 )
2014-01-03 13:32:13 -05:00
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
#- Subscription callbacks
onToggleDebug: (e) ->
e ? . preventDefault ? ( )
Backbone . Mediator . publish ' level:set-debug ' , { debug: not @ debug }
onSetDebug: (e) ->
return if e . debug is @ debug
@debug = e . debug
if @ debug and not @ debugDisplay
@ screenLayer . addChild @debugDisplay = new DebugDisplay canvasWidth: @ camera . canvasWidth , canvasHeight: @ camera . canvasHeight
2014-01-03 13:32:13 -05:00
onLevelRestarted: (e) ->
@ setProgress 0 , 0
onSetCamera: (e) ->
if e . thangID
2014-05-14 18:59:56 -04:00
return unless target = @ spriteBoss . spriteFor ( e . thangID ) ? . imageObject
2014-01-03 13:32:13 -05:00
else if e . pos
target = @ camera . worldToSurface e . pos
else
target = null
@ camera . setBounds e . bounds if e . bounds
@ cameraBorder . updateBounds @ camera . bounds
2014-02-19 15:43:25 -05:00
@ camera . zoomTo target , e . zoom , e . duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
2014-01-03 13:32:13 -05:00
2014-05-12 18:54:07 -04:00
onZoomUpdated: (e) ->
if @ ended
@ setPaused false
@surfaceZoomPauseTimeout = _ . delay ( => @ setPaused true ) , 3000
2014-01-03 13:32:13 -05:00
onDisableControls: (e) ->
return if e . controls and not ( ' surface ' in e . controls )
@ setDisabled true
@ dimmer ? = new Dimmer camera: @ camera , layer: @ screenLayer
@ dimmer . setSprites @ spriteBoss . sprites
onEnableControls: (e) ->
return if e . controls and not ( ' surface ' in e . controls )
@ setDisabled false
onSetLetterbox: (e) ->
@ setDisabled e . on
2014-09-05 12:20:29 -04:00
setDisabled: (@disabled) ->
@spriteBoss.disabled = @ disabled
2014-02-11 15:54:08 -05:00
onSetPlaying: (e) ->
2014-01-03 13:32:13 -05:00
@playing = ( e ? { } ) . playing ? true
2014-05-20 15:21:43 -04:00
@setPlayingCalled = true
2014-01-03 13:32:13 -05:00
if @ playing and @ currentFrame >= ( @ world . totalFrames - 5 )
@currentFrame = 0
2014-08-23 12:52:05 -04:00
if @ fastForwardingToFrame and not @ playing
@fastForwardingToFrame = null
2014-01-03 13:32:13 -05:00
2014-02-11 15:54:08 -05:00
onSetTime: (e) ->
2014-01-03 13:32:13 -05:00
toFrame = @ currentFrame
if e . time ?
2014-08-22 20:11:40 -04:00
@worldLifespan = @ world . frames . length / @ world . frameRate
2014-01-03 13:32:13 -05:00
e.ratio = e . time / @ worldLifespan
if e . ratio ?
2014-08-22 20:11:40 -04:00
toFrame = @ world . frames . length * e . ratio
2014-01-03 13:32:13 -05:00
if e . frameOffset
toFrame += e . frameOffset
if e . ratioOffset
2014-08-22 20:11:40 -04:00
toFrame += @ world . frames . length * e . ratioOffset
2014-01-03 13:32:13 -05:00
unless _ . isNumber ( toFrame ) and not _ . isNaN ( toFrame )
return console . error ( ' set-time event ' , e , ' produced invalid target frame ' , toFrame )
2014-08-22 20:11:40 -04:00
@ setProgress ( toFrame / @ world . frames . length , e . scrubDuration )
2014-01-03 13:32:13 -05:00
2014-05-11 20:42:32 -04:00
onCastSpells: (e) ->
return if e . preload
2014-05-12 18:54:07 -04:00
@ setPaused false if @ ended
2014-02-24 17:40:28 -05:00
@casting = true
2014-08-22 00:23:45 -04:00
@setPlayingCalled = false # Don't overwrite playing settings if they changed by, say, scripts.
@frameBeforeCast = @ currentFrame
2014-09-25 01:07:55 -04:00
# This is where I wanted to trigger a rewind, but it turned out to be pretty complicated, since the new world gets updated everywhere, and you don't want to rewind through that.
@ setProgress 0 , 0
2014-01-03 13:32:13 -05:00
onNewWorld: (event) ->
return unless event . world . name is @ world . name
2014-08-26 16:56:57 -04:00
@ onStreamingWorldUpdated event
onStreamingWorldUpdated: (event) ->
2014-02-24 17:40:28 -05:00
@casting = false
2014-08-22 00:23:45 -04:00
@ spriteBoss . play ( )
2014-02-28 14:27:32 -05:00
2014-02-25 13:50:12 -05:00
# This has a tendency to break scripts that are waiting for playback to change when the level is loaded
# so only run it after the first world is created.
2014-08-27 15:24:03 -04:00
Backbone . Mediator . publish ' level:set-playing ' , { playing: true } unless event . firstWorld or @ setPlayingCalled
2014-08-22 00:23:45 -04:00
@ setWorld event . world
@ onFrameChanged ( true )
2014-08-24 01:24:00 -04:00
fastForwardBuffer = 2
2014-09-25 00:01:58 -04:00
if @ playing and not @ realTime and ( ffToFrame = Math . min ( event . firstChangedFrame , @ frameBeforeCast , @ world . frames . length - 1 ) ) and ffToFrame > @ currentFrame + fastForwardBuffer * @ world . frameRate
2014-08-23 12:52:05 -04:00
@fastForwardingToFrame = ffToFrame
2014-09-25 01:07:55 -04:00
@fastForwardingSpeed = Math . max 3 , 3 * ( @ world . maxTotalFrames * @ world . dt ) / 60
2014-08-24 01:24:00 -04:00
else if @ realTime
lag = ( @ world . frames . length - 1 ) * @ world . dt - @ world . age
intendedLag = @ world . realTimeBufferMax + @ world . dt
if lag > intendedLag * 1.2
@fastForwardingToFrame = @ world . frames . length - @ world . realTimeBufferMax * @ world . frameRate
@fastForwardingSpeed = lag / intendedLag
else
@fastForwardingToFrame = @fastForwardingSpeed = null
2014-09-05 12:20:29 -04:00
#console.log "on new world, lag", lag, "intended lag", intendedLag, "fastForwardingToFrame", @fastForwardingToFrame, "speed", @fastForwardingSpeed, "cause we are at", @world.age, "of", @world.frames.length * @world.dt
2014-01-03 13:32:13 -05:00
2014-09-05 12:20:29 -04:00
onIdleChanged: (e) ->
@ setPaused e . idle unless @ ended
2014-01-03 13:32:13 -05:00
2014-09-05 12:20:29 -04:00
#- Mouse event callbacks
2014-01-03 13:32:13 -05:00
onMouseMove: (e) =>
2014-05-14 18:29:55 -04:00
@mouseScreenPos = { x: e . stageX , y: e . stageY }
2014-01-03 13:32:13 -05:00
return if @ disabled
Backbone . Mediator . publish ' surface:mouse-moved ' , x: e . stageX , y: e . stageY
onMouseDown: (e) =>
return if @ disabled
2014-09-11 16:05:20 -04:00
cap = @ camera . screenToCanvas ( { x: e . stageX , y: e . stageY } )
2014-09-02 19:41:31 -04:00
# getObject(s)UnderPoint is broken, so we have to use the private method to get what we want
2014-09-11 16:05:20 -04:00
onBackground = not @ stage . _getObjectsUnderPoint ( e . stageX , e . stageY , null , true )
2014-09-03 21:35:14 -04:00
2014-09-11 16:05:20 -04:00
wop = @ camera . screenToWorld x: e . stageX , y: e . stageY
event = onBackground: onBackground , x: e . stageX , y: e . stageY , originalEvent: e , worldPos: wop
2014-08-23 20:26:56 -04:00
Backbone . Mediator . publish ' surface:stage-mouse-down ' , event
2014-08-28 17:00:54 -04:00
Backbone . Mediator . publish ' tome:focus-editor ' , { }
2014-09-21 23:49:45 -04:00
@mouseIsDown = true
2014-01-03 13:32:13 -05:00
2014-05-20 19:20:24 -04:00
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
2014-08-28 17:00:54 -04:00
Backbone . Mediator . publish ' tome:focus-editor ' , { }
2014-09-21 23:49:45 -04:00
@mouseIsDown = false
2014-05-20 19:20:24 -04:00
2014-02-12 15:41:41 -05:00
onMouseWheel: (e) =>
# https://github.com/brandonaaron/jquery-mousewheel
e . preventDefault ( )
return if @ disabled
2014-02-15 16:45:16 -05:00
event =
deltaX: e . deltaX
deltaY: e . deltaY
2014-05-16 18:33:49 -04:00
canvas: @ canvas
2014-08-27 21:05:18 -04:00
event.screenPos = @ mouseScreenPos if @ mouseScreenPos
2014-02-15 16:45:16 -05:00
Backbone . Mediator . publish ' surface:mouse-scrolled ' , event unless @ disabled
2014-01-03 13:32:13 -05:00
2014-09-05 12:20:29 -04:00
#- Canvas callbacks
2014-01-03 13:32:13 -05:00
2014-09-05 12:20:29 -04:00
onResize: (e) =>
2014-09-15 18:38:07 -04:00
return if @ destroyed or @ options . choosing
2014-09-05 12:20:29 -04:00
oldWidth = parseInt @ canvas . attr ( ' width ' ) , 10
oldHeight = parseInt @ canvas . attr ( ' height ' ) , 10
aspectRatio = oldWidth / oldHeight
pageWidth = $ ( ' # page-container ' ) . width ( ) - 17 # 17px nano scroll bar
if @ realTime or @ options . spectateGame
pageHeight = $ ( ' # page-container ' ) . height ( ) - $ ( ' # control-bar-view ' ) . outerHeight ( ) - $ ( ' # playback-view ' ) . outerHeight ( )
newWidth = Math . min pageWidth , pageHeight * aspectRatio
newHeight = newWidth / aspectRatio
else if $ ( ' # thangs-tab-view ' )
newWidth = $ ( ' # canvas-wrapper ' ) . width ( )
newHeight = newWidth / aspectRatio
else
newWidth = 0.55 * pageWidth
newHeight = newWidth / aspectRatio
return unless newWidth > 0 and newHeight > 0
##if InstallTrigger? # Firefox rendering performance goes down as canvas size goes up
## newWidth = Math.min 924, newWidth
## newHeight = Math.min 589, newHeight
#@canvas.width newWidth
#@canvas.height newHeight
2014-09-06 22:50:31 -04:00
scaleFactor = if application . isIPadApp then 2 else 1 # Retina
@ canvas . attr width: newWidth * scaleFactor , height: newHeight * scaleFactor
2014-09-05 12:20:29 -04:00
@ stage . scaleX *= newWidth / oldWidth
@ stage . scaleY *= newHeight / oldHeight
@ camera . onResize newWidth , newHeight
2014-01-03 13:32:13 -05:00
2014-09-21 23:49:45 -04:00
#- Camera focus on hero
focusOnHero: ->
@heroSprite = @ spriteBoss . spriteFor ' Hero Placeholder '
2014-01-03 13:32:13 -05:00
2014-09-05 12:20:29 -04:00
#- Real-time playback
2014-09-06 22:50:31 -04:00
2014-08-30 00:46:26 -04:00
onRealTimePlaybackWaiting: (e) ->
@ onRealTimePlaybackStarted e
2014-08-23 16:54:52 -04:00
onRealTimePlaybackStarted: (e) ->
2014-08-30 00:46:26 -04:00
return if @ realTime
2014-08-23 16:54:52 -04:00
@realTime = true
@ onResize ( )
2014-08-23 22:00:35 -04:00
@ spriteBoss . selfWizardSprite ? . toggle false
2014-08-26 01:05:24 -04:00
@playing = false # Will start when countdown is done.
2014-09-24 01:10:18 -04:00
if @ heroSprite
@previousCameraZoom = @ camera . zoom
@ camera . zoomTo @ heroSprite . imageObject , 4 , 3000
2014-08-23 16:54:52 -04:00
onRealTimePlaybackEnded: (e) ->
2014-08-31 19:05:15 -04:00
return unless @ realTime
2014-08-23 16:54:52 -04:00
@realTime = false
@ onResize ( )
2014-08-23 22:00:35 -04:00
@ spriteBoss . selfWizardSprite ? . toggle true
2014-08-24 19:09:06 -04:00
@ canvas . removeClass ' flag-color-selected '
2014-09-24 01:10:18 -04:00
if @ previousCameraZoom
@ camera . zoomTo @ camera . newTarget or @ camera . target , @ previousCameraZoom , 3000
2014-08-23 20:26:56 -04:00
2014-08-24 19:09:06 -04:00
onFlagColorSelected: (e) ->
@ canvas . toggleClass ' flag-color-selected ' , Boolean ( e . color )
e.pos = @ camera . screenToWorld @ mouseScreenPos if @ mouseScreenPos
2014-08-23 16:54:52 -04:00
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
#- Paths - TODO: move to SpriteBoss? but only update on frame drawing instead of on every frame update?
2014-01-03 13:32:13 -05:00
updatePaths: ->
return unless @ options . paths
2014-02-24 17:40:28 -05:00
return if @ casting
2014-01-03 13:32:13 -05:00
@ hidePaths ( )
selectedThang = @ spriteBoss . selectedSprite ? . thang
return if @ world . showPaths is ' never '
return if @ world . showPaths is ' paused ' and @ playing
return if @ world . showPaths is ' selected ' and not selectedThang
@ trailmaster ? = new path . Trailmaster @ camera
2014-06-30 22:16:26 -04:00
selectedOnly = @ playing and @ world . showPaths is ' selected '
2014-01-03 13:32:13 -05:00
@paths = @ trailmaster . generatePaths @ world , @ getCurrentFrame ( ) , selectedThang , @ spriteBoss . sprites , selectedOnly
@paths.name = ' paths '
2014-06-30 22:16:26 -04:00
@ spriteBoss . spriteLayers [ ' Path ' ] . addChild @ paths
2014-01-03 13:32:13 -05:00
hidePaths: ->
return if not @ paths
@ paths . parent . removeChild @ paths
@paths = null
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
#- Screenshot
2014-01-03 13:32:13 -05:00
screenshot: (scale=0.25, format='image/jpeg', quality=0.8, zoom=2) ->
# Quality doesn't work with image/png, just image/jpeg and image/webp
[ w , h ] = [ @ camera . canvasWidth , @ camera . canvasHeight ]
margin = ( 1 - 1 / zoom ) / 2
@ stage . cache margin * w , margin * h , w / zoom , h / zoom , scale * zoom
imageData = @ stage . cacheCanvas . toDataURL ( format , quality )
2014-06-30 22:16:26 -04:00
#console.log 'Screenshot with scale', scale, 'format', format, 'quality', quality, 'was', Math.floor(imageData.length / 1024), 'kB'
screenshot = document . createElement ( ' img ' )
2014-01-03 13:32:13 -05:00
screenshot.src = imageData
@ stage . uncache ( )
imageData
2014-09-05 12:20:29 -04:00
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
#- Path finding debugging
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
onTogglePathFinding: (e) ->
e ? . preventDefault ? ( )
@ hidePathFinding ( )
@showingPathFinding = not @ showingPathFinding
if @ showingPathFinding then @ showPathFinding ( ) else @ hidePathFinding ( )
hidePathFinding: ->
@ surfaceLayer . removeChild @ navRectangles if @ navRectangles
@ surfaceLayer . removeChild @ navPaths if @ navPaths
@navRectangles = @navPaths = null
showPathFinding: ->
@ hidePathFinding ( )
mesh = _ . values ( @ world . navMeshes or { } ) [ 0 ]
return unless mesh
@navRectangles = new createjs . Container ( )
@navRectangles.layerPriority = - 1
@ addMeshRectanglesToContainer mesh , @ navRectangles
@ surfaceLayer . addChild @ navRectangles
@ surfaceLayer . updateLayerOrder ( )
graph = _ . values ( @ world . graphs or { } ) [ 0 ]
return @ surfaceLayer . updateLayerOrder ( ) unless graph
@navPaths = new createjs . Container ( )
@navPaths.layerPriority = - 1
@ addNavPathsToContainer graph , @ navPaths
@ surfaceLayer . addChild @ navPaths
@ surfaceLayer . updateLayerOrder ( )
addMeshRectanglesToContainer: (mesh, container) ->
for rect in mesh
shape = new createjs . Shape ( )
pos = @ camera . worldToSurface { x: rect . x , y: rect . y }
dim = @ camera . worldToSurface { x: rect . width , y: rect . height }
shape . graphics
. setStrokeStyle ( 3 )
. beginFill ( ' rgba(0,0,128,0.3) ' )
. beginStroke ( ' rgba(0,0,128,0.7) ' )
. drawRect ( pos . x - dim . x / 2 , pos . y - dim . y / 2 , dim . x , dim . y )
container . addChild shape
addNavPathsToContainer: (graph, container) ->
for node in _ . values graph
for edgeVertex in node . edges
@ drawLine node . vertex , edgeVertex , container
drawLine: (v1, v2, container) ->
shape = new createjs . Shape ( )
v1 = @ camera . worldToSurface v1
v2 = @ camera . worldToSurface v2
shape . graphics
. setStrokeStyle ( 1 )
. moveTo ( v1 . x , v1 . y )
. beginStroke ( ' rgba(128,0,0,0.4) ' )
. lineTo ( v2 . x , v2 . y )
. endStroke ( )
container . addChild shape
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
#- Teardown
2014-09-06 22:50:31 -04:00
2014-09-05 12:20:29 -04:00
destroy: ->
@ camera ? . destroy ( )
createjs . Ticker . removeEventListener ( ' tick ' , @ tick )
createjs . Sound . stop ( )
layer . destroy ( ) for layer in @ layers
@ spriteBoss . destroy ( )
@ chooser ? . destroy ( )
@ dimmer ? . destroy ( )
@ countdownScreen ? . destroy ( )
@ playbackOverScreen ? . destroy ( )
@ waitingScreen ? . destroy ( )
@ coordinateDisplay ? . destroy ( )
@ coordinateGrid ? . destroy ( )
@ stage . clear ( )
@ musicPlayer ? . destroy ( )
@ stage . removeAllChildren ( )
@ stage . removeEventListener ' stagemousemove ' , @ onMouseMove
@ stage . removeEventListener ' stagemousedown ' , @ onMouseDown
@ stage . removeEventListener ' stagemouseup ' , @ onMouseUp
@ stage . removeAllEventListeners ( )
@ stage . enableDOMEvents false
@ stage . enableMouseOver 0
@ canvas . off ' mousewheel ' , @ onMouseWheel
$ ( window ) . off ' resize ' , @ onResize
clearTimeout @ surfacePauseTimeout if @ surfacePauseTimeout
clearTimeout @ surfaceZoomPauseTimeout if @ surfaceZoomPauseTimeout
super ( )