2014-01-03 10:32:13 -08: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-02-24 14:40:28 -08:00
CastingScreen = require ' ./CastingScreen '
2014-02-25 17:14:39 -08:00
PlaybackOverScreen = require ' ./PlaybackOverScreen '
2014-01-03 10:32:13 -08:00
DebugDisplay = require ' ./DebugDisplay '
CoordinateDisplay = require ' ./CoordinateDisplay '
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
gridLayer: null # TODO: maybe
spriteBoss: null
debugDisplay: null
currentFrame: 0
lastFrame: null
totalFramesDrawn: 0
playing: true # play vs. pause
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'
coords: true
playJingle: false
showInvisible: false
2014-03-01 11:21:24 -08:00
frameRate: 60 # Best as a divisor of 60, like 15, 30, 60, with RAF_SYNCHED timing.
2014-01-03 10:32:13 -08:00
subscriptions:
' level-disable-controls ' : ' onDisableControls '
' level-enable-controls ' : ' onEnableControls '
' level-set-playing ' : ' onSetPlaying '
' level-set-debug ' : ' onSetDebug '
' level-toggle-debug ' : ' onToggleDebug '
' level-set-grid ' : ' onSetGrid '
' level-toggle-grid ' : ' onToggleGrid '
2014-01-31 16:32:46 -08:00
' level-toggle-pathfinding ' : ' onTogglePathFinding '
2014-01-03 10:32:13 -08:00
' level-set-time ' : ' onSetTime '
' level-set-surface-camera ' : ' onSetCamera '
' level:restarted ' : ' onLevelRestarted '
' god:new-world-created ' : ' onNewWorld '
' tome:cast-spells ' : ' onCastSpells '
' level-set-letterbox ' : ' onSetLetterbox '
shortcuts:
2014-01-31 16:32:46 -08:00
' ctrl+ \\ , ⌘+ \\ ' : ' onToggleDebug '
' ctrl+g, ⌘+g ' : ' onToggleGrid '
' ctrl+o, ⌘+o ' : ' onTogglePathFinding '
2014-01-03 10:32:13 -08:00
# external functions
constructor: (@world, @canvas, givenOptions) ->
super ( )
@layers = [ ]
@options = _ . clone ( @ defaults )
@options = _ . extend ( @ options , givenOptions ) if givenOptions
@ initEasel ( )
@ initAudio ( )
destroy: ->
@dead = true
2014-02-11 14:58:45 -08:00
@ camera ? . destroy ( )
2014-01-03 10:32:13 -08:00
createjs . Ticker . removeEventListener ( " tick " , @ tick )
createjs . Sound . stop ( )
layer . destroy ( ) for layer in @ layers
@ spriteBoss . destroy ( )
@ chooser ? . destroy ( )
@ dimmer ? . destroy ( )
2014-02-24 14:40:28 -08:00
@ castingScreen ? . destroy ( )
2014-02-25 17:14:39 -08:00
@ playbackOverScreen ? . destroy ( )
2014-01-03 10:32:13 -08:00
@ stage . clear ( )
@ musicPlayer ? . destroy ( )
2014-02-12 12:41:41 -08:00
@ stage . removeAllChildren ( )
2014-02-11 12:32:12 -08:00
@ stage . removeEventListener ' stagemousemove ' , @ onMouseMove
@ stage . removeEventListener ' stagemousedown ' , @ onMouseDown
@ stage . removeAllEventListeners ( )
2014-02-12 12:41:41 -08:00
@ stage . enableDOMEvents false
@ stage . enableMouseOver 0
2014-02-28 11:27:32 -08:00
@onFramesScrubbed = null
2014-02-11 14:24:06 -08:00
@onMouseMove = null
@onMouseDown = null
@tick = null
2014-02-12 12:41:41 -08:00
@ canvas . off ' mousewheel ' , @ onMouseWheel
@onMouseWheel = null
2014-02-14 10:57:47 -08:00
super ( )
2014-02-12 12:41:41 -08:00
2014-01-03 10:32:13 -08:00
setWorld: (@world) ->
@worldLoaded = true
@ world . getFrame ( Math . min ( @ getCurrentFrame ( ) , @ world . totalFrames - 1 ) ) . restoreState ( ) unless @ options . choosing
@spriteBoss.world = @ world
@ showLevel ( )
@ updateState true if @ loaded
# TODO: synchronize both ways of choosing whether to show coords (@world via UI System or @options via World Select modal)
if @ world . showCoordinates and @ options . coords
@ surfaceTextLayer . addChild new CoordinateDisplay camera: @ camera
@ onFrameChanged ( )
Backbone . Mediator . publish ' surface:world-set-up '
2014-01-31 16:32:46 -08:00
onTogglePathFinding: (e) ->
e ? . preventDefault ? ( )
2014-01-20 12:57:45 -08:00
@ hidePathFinding ( )
@showingPathFinding = not @ showingPathFinding
if @ showingPathFinding then @ showPathFinding ( ) else @ hidePathFinding ( )
2014-01-31 10:21:32 -08:00
2014-01-20 12:57:45 -08:00
hidePathFinding: ->
@ surfaceLayer . removeChild @ navRectangles if @ navRectangles
@ surfaceLayer . removeChild @ navPaths if @ navPaths
@navRectangles = @navPaths = null
2014-01-31 10:21:32 -08:00
2014-01-20 12:57:45 -08:00
showPathFinding: ->
@ hidePathFinding ( )
2014-01-31 10:21:32 -08:00
2014-01-20 12:57:45 -08:00
mesh = _ . values ( @ world . navMeshes or { } ) [ 0 ]
return unless mesh
@navRectangles = new createjs . Container ( )
@navRectangles.layerPriority = - 1
@ addMeshRectanglesToContainer mesh , @ navRectangles
@ surfaceLayer . addChild @ navRectangles
@ surfaceLayer . updateLayerOrder ( )
2014-01-31 10:21:32 -08:00
2014-01-20 16:50:53 -08:00
graph = _ . values ( @ world . graphs or { } ) [ 0 ]
return @ surfaceLayer . updateLayerOrder ( ) unless graph
2014-01-20 12:57:45 -08:00
@navPaths = new createjs . Container ( )
@navPaths.layerPriority = - 1
2014-01-20 16:50:53 -08:00
@ addNavPathsToContainer graph , @ navPaths
2014-01-20 12:57:45 -08:00
@ surfaceLayer . addChild @ navPaths
@ surfaceLayer . updateLayerOrder ( )
2014-01-31 10:21:32 -08:00
2014-01-20 12:57:45 -08:00
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-01-03 10:32:13 -08:00
setProgress: (progress, scrubDuration=500) ->
2014-02-25 17:14:39 -08:00
progress = Math . max ( Math . min ( progress , 1 ) , 0.0 )
2014-01-03 10:32:13 -08:00
@scrubbing = true
onTweenEnd = =>
@scrubbingTo = null
@scrubbing = false
@scrubbingPlaybackSpeed = null
@fastForwarding = false
if @ scrubbingTo ?
# cut to the chase for existing tween
createjs . Tween . removeTweens ( @ )
@currentFrame = @ scrubbingTo
2014-02-25 17:14:39 -08:00
@scrubbingTo = Math . min ( Math . floor ( progress * @ world . totalFrames ) , @ world . totalFrames )
2014-01-03 10:32:13 -08:00
@scrubbingPlaybackSpeed = Math . sqrt ( Math . abs ( @ scrubbingTo - @ currentFrame ) * @ world . dt / ( scrubDuration or 0.5 ) )
if scrubDuration
t = createjs . Tween
. get ( @ )
. to ( { currentFrame : @ scrubbingTo } , scrubDuration , createjs . Ease . sineInOut )
. call ( onTweenEnd )
2014-02-28 11:27:32 -08:00
t . addEventListener ( ' change ' , @ onFramesScrubbed )
2014-01-03 10:32:13 -08:00
else
@currentFrame = @ scrubbingTo
2014-02-28 11:27:32 -08:00
@ onFramesScrubbed ( ) # For performance, don't play these for instant transitions.
2014-01-03 10:32:13 -08:00
onTweenEnd ( )
@ updateState true
@ onFrameChanged ( )
2014-02-28 11:27:32 -08:00
onFramesScrubbed: (e) =>
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 ( )
for thangID , sprite of @ spriteBoss . sprites
sprite . playSounds false , Math . max ( 0.05 , Math . min ( 1 , 1 / @ scrubbingPlaybackSpeed ) )
tempFrame += if rising then 1 else - 1
@currentFrame = actualCurrentFrame
@ restoreWorldState ( )
2014-02-24 17:01:36 -08:00
@ spriteBoss . update true
2014-01-03 10:32:13 -08:00
@ onFrameChanged ( )
getCurrentFrame: ->
2014-02-17 11:53:52 -08:00
return Math . max ( 0 , Math . min ( Math . floor ( @ currentFrame ) , @ world . totalFrames - 1 ) )
2014-01-03 10:32:13 -08:00
getProgress: -> @ currentFrame / @ world . totalFrames
onLevelRestarted: (e) ->
@ setProgress 0 , 0
onSetCamera: (e) ->
if e . thangID
return unless target = @ spriteBoss . spriteFor ( e . thangID ) ? . displayObject
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 12:43:25 -08:00
@ camera . zoomTo target , e . zoom , e . duration # TODO: SurfaceScriptModule perhaps shouldn't assign e.zoom if not set
2014-01-03 10:32:13 -08:00
setDisabled: (@disabled) ->
@spriteBoss.disabled = @ disabled
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-02-11 12:54:08 -08:00
onSetPlaying: (e) ->
2014-01-03 10:32:13 -08:00
@playing = ( e ? { } ) . playing ? true
if @ playing and @ currentFrame >= ( @ world . totalFrames - 5 )
@currentFrame = 0
if @ fastForwarding and not @ playing
@ setProgress @ currentFrame / @ world . totalFrames
2014-02-11 12:54:08 -08:00
onSetTime: (e) ->
2014-01-03 10:32:13 -08:00
toFrame = @ currentFrame
if e . time ?
@worldLifespan = @ world . totalFrames / @ world . frameRate
e.ratio = e . time / @ worldLifespan
if e . ratio ?
toFrame = @ world . totalFrames * e . ratio
if e . frameOffset
toFrame += e . frameOffset
if e . ratioOffset
toFrame += @ world . totalFrames * e . ratioOffset
unless _ . isNumber ( toFrame ) and not _ . isNaN ( toFrame )
return console . error ( ' set-time event ' , e , ' produced invalid target frame ' , toFrame )
@ setProgress ( toFrame / @ world . totalFrames , e . scrubDuration )
onFrameChanged: (force) ->
@currentFrame = Math . min ( @ currentFrame , @ world . totalFrames )
@ debugDisplay ? . updateFrame @ currentFrame
return if @ currentFrame is @ lastFrame and not force
progress = @ getProgress ( )
Backbone . Mediator . publish ( ' surface:frame-changed ' ,
type: " frame-changed "
selectedThang: @ spriteBoss . selectedSprite ? . thang
progress: progress
frame: @ currentFrame
world: @ world
)
2014-02-25 17:14:39 -08:00
2014-03-07 21:02:10 -08:00
if @ lastFrame < @ world . totalFrames and @ currentFrame >= @ world . totalFrames - 1
2014-02-25 17:14:39 -08:00
@ spriteBoss . stop ( )
@ playbackOverScreen . show ( )
@ended = true
Backbone . Mediator . publish ' surface:playback-ended '
else if @ currentFrame < @ world . totalFrames and @ ended
@ spriteBoss . play ( )
@ playbackOverScreen . hide ( )
@ended = false
Backbone . Mediator . publish ' surface:playback-restarted '
2014-01-03 10:32:13 -08:00
@lastFrame = @ currentFrame
onCastSpells: (event) ->
2014-02-24 14:40:28 -08:00
@casting = true
@wasPlayingWhenCastingBegan = @ playing
Backbone . Mediator . publish ' level-set-playing ' , { playing: false }
2014-02-24 17:01:36 -08:00
2014-01-03 10:32:13 -08:00
createjs . Tween . removeTweens ( @ surfaceLayer )
createjs . Tween . get ( @ surfaceLayer ) . to ( { alpha : 0.9 } , 1000 , createjs . Ease . getPowOut ( 4.0 ) )
onNewWorld: (event) ->
return unless event . world . name is @ world . name
2014-02-24 14:40:28 -08:00
@casting = false
2014-02-28 11:27:32 -08:00
2014-02-25 10:50:12 -08: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.
Backbone . Mediator . publish ' level-set-playing ' , { playing: @ wasPlayingWhenCastingBegan } unless event . firstWorld
2014-02-24 17:01:36 -08:00
2014-01-03 10:32:13 -08:00
fastForwardTo = null
if @ playing
fastForwardTo = Math . min event . world . firstChangedFrame , @ currentFrame
@currentFrame = 0
createjs . Tween . removeTweens ( @ surfaceLayer )
f = =>
@ setWorld event . world
@ onFrameChanged ( true )
if fastForwardTo and @ playing
fastForwardToRatio = fastForwardTo / @ world . totalFrames
fastForwardToTime = fastForwardTo * @ world . dt
fastForwardSpeed = Math . max 4 , fastForwardToTime / 3
@ setProgress fastForwardToRatio , 1000 * fastForwardToTime / fastForwardSpeed
@fastForwarding = true
createjs . Tween . get ( @ surfaceLayer )
. to ( { alpha : 0.0 } , 50 )
. call ( f )
. to ( { alpha : 1.0 } , 2000 , createjs . Ease . getPowOut ( 2.0 ) )
# initialization
initEasel: ->
# takes DOM objects, not jQuery objects
@stage = new createjs . Stage ( @ canvas [ 0 ] )
canvasWidth = parseInt ( @ canvas . attr ( ' width ' ) , 10 )
canvasHeight = parseInt ( @ canvas . attr ( ' height ' ) , 10 )
2014-02-11 14:58:45 -08:00
@ camera ? . destroy ( )
2014-01-03 10:32:13 -08:00
@camera = new Camera canvasWidth , canvasHeight
2014-03-05 19:39:14 -08:00
AudioPlayer.camera = @ camera
2014-01-03 10:32:13 -08:00
@ 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 @screenLayer = new Layer name: " Screen " , layerPriority: 2 , 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
2014-02-24 14:40:28 -08:00
@ castingScreen ? = new CastingScreen camera: @ camera , layer: @ screenLayer
2014-02-25 17:14:39 -08:00
@ playbackOverScreen ? = new PlaybackOverScreen camera: @ camera , layer: @ screenLayer
2014-01-03 10:32:13 -08:00
@ stage . enableMouseOver ( 10 )
@ stage . addEventListener ' stagemousemove ' , @ onMouseMove
@ stage . addEventListener ' stagemousedown ' , @ onMouseDown
2014-02-12 12:41:41 -08:00
@ canvas . on ' mousewheel ' , @ onMouseWheel
2014-01-03 10:32:13 -08:00
@ hookUpChooseControls ( ) if @ options . choosing
2014-02-28 21:29:14 -08:00
createjs.Ticker.timingMode = createjs . Ticker . RAF_SYNCHED
2014-02-28 17:12:23 -08:00
createjs . Ticker . setFPS @ options . frameRate
2014-01-03 10:32:13 -08:00
showLevel: ->
return if @ dead
return unless @ worldLoaded
return if @ loaded
@loaded = true
@ spriteBoss . createMarks ( )
@ spriteBoss . createIndieSprites @ world . indieSprites , @ options . wizards
Backbone . Mediator . publish ' registrar-echo-states '
@ updateState true
@ drawCurrentFrame ( )
@ showGrid ( ) if @ options . grid # TODO: pay attention to world grid setting (which we only know when world simulates)
createjs . Ticker . addEventListener " tick " , @ tick
Backbone . Mediator . publish ' level:started '
2014-02-22 12:01:05 -08:00
createOpponentWizard: (opponent) ->
@ spriteBoss . createOpponentWizard opponent
2014-01-03 10:32:13 -08:00
initAudio: ->
@musicPlayer = new MusicPlayer ( )
# grid; should probably refactor into separate class
showGrid: ->
return if @ gridShowing ( )
unless @ gridLayer
@gridLayer = new createjs . Container ( )
@gridShape = new createjs . Shape ( )
@ gridLayer . addChild @ gridShape
@gridLayer.z = 90019001
@gridLayer.mouseEnabled = false
@gridShape.alpha = 0.125
@ gridShape . graphics . beginStroke " blue "
gridSize = Math . round ( @ world . size ( ) [ 0 ] / 20 )
wopStart = x: 0 , y: 0
wopEnd = x: @ world . size ( ) [ 0 ] , y: @ world . size ( ) [ 1 ]
supStart = @ camera . worldToSurface wopStart
supEnd = @ camera . worldToSurface wopEnd
wop = x: wopStart . x , y: wopStart . y
while wop . x < wopEnd . x
sup = @ camera . worldToSurface wop
@ gridShape . graphics . mt ( sup . x , supStart . y ) . lt ( sup . x , supEnd . y )
t = new createjs . Text ( wop . x . toFixed ( 0 ) , " 16px Arial " , " blue " )
t.x = sup . x - t . getMeasuredWidth ( ) / 2
t.y = supStart . y - 10 - t . getMeasuredHeight ( ) / 2
t.alpha = 0.75
@ gridLayer . addChild t
wop . x += gridSize
while wop . y < wopEnd . y
sup = @ camera . worldToSurface wop
@ gridShape . graphics . mt ( supStart . x , sup . y ) . lt ( supEnd . x , sup . y )
t = new createjs . Text ( wop . y . toFixed ( 0 ) , " 16px Arial " , " blue " )
t.x = 10 - t . getMeasuredWidth ( ) / 2
t.y = sup . y - t . getMeasuredHeight ( ) / 2
t.alpha = 0.75
@ gridLayer . addChild t
wop . y += gridSize
@ gridShape . graphics . endStroke ( )
bounds = @ gridLayer . getBounds ( )
return unless bounds ? . width and bounds . height
@ gridLayer . cache bounds . x , bounds . y , bounds . width , bounds . height
@ surfaceLayer . addChild @ gridLayer
hideGrid: ->
return unless @ gridShowing ( )
@ gridLayer . parent . removeChild @ gridLayer
gridShowing: ->
@ gridLayer ? . parent ?
2014-01-31 16:32:46 -08:00
onToggleGrid: (e) ->
e ? . preventDefault ? ( )
2014-01-03 10:32:13 -08:00
if @ gridShowing ( ) then @ hideGrid ( ) else @ showGrid ( )
onSetGrid: (e) ->
if e . grid then @ showGrid ( ) else @ hideGrid ( )
onToggleDebug: (e) ->
2014-01-31 16:32:46 -08:00
e ? . preventDefault ? ( )
2014-01-03 10:32:13 -08:00
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
# uh
onMouseMove: (e) =>
2014-02-15 13:45:16 -08:00
@mouseSurfacePos = { x : e . stageX , y : e . stageY }
2014-01-03 10:32:13 -08:00
return if @ disabled
Backbone . Mediator . publish ' surface:mouse-moved ' , x: e . stageX , y: e . stageY
onMouseDown: (e) =>
return if @ disabled
onBackground = not @ stage . hitTest e . stageX , e . stageY
Backbone . Mediator . publish ' surface:stage-mouse-down ' , onBackground: onBackground , x: e . stageX , y: e . stageY , originalEvent: e
2014-02-12 12:41:41 -08:00
onMouseWheel: (e) =>
# https://github.com/brandonaaron/jquery-mousewheel
e . preventDefault ( )
return if @ disabled
2014-02-15 13:45:16 -08:00
event =
deltaX: e . deltaX
deltaY: e . deltaY
surfacePos: @ mouseSurfacePos
Backbone . Mediator . publish ' surface:mouse-scrolled ' , event unless @ disabled
2014-01-03 10:32:13 -08: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
# Main Surface update loop
tick: (e) =>
# seems to be a bug where only one object can register with the Ticker...
oldFrame = @ currentFrame
2014-02-28 11:27:32 -08:00
oldWorldFrame = Math . floor oldFrame
2014-03-10 08:45:36 -07:00
lastFrame = @ world . totalFrames - 1
2014-01-03 10:32:13 -08:00
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)
2014-03-10 08:45:36 -07:00
frameAdvanced = ( @ playing and @ currentFrame < lastFrame ) or @ totalFramesDrawn < 2
if frameAdvanced and @ playing
@ currentFrame += @ world . frameRate / @ options . frameRate
@currentFrame = Math . min @ currentFrame , lastFrame
2014-02-28 11:27:32 -08:00
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
2014-01-03 10:32:13 -08:00
break unless Dropper . drop ( )
2014-02-28 11:27:32 -08:00
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-03 10:32:13 -08:00
# these are skipped for dropped frames
@ updateState @ currentFrame isnt oldFrame
2014-02-17 11:53:52 -08:00
@ drawCurrentFrame e
2014-01-03 10:32:13 -08:00
@ onFrameChanged ( )
2014-02-17 11:53:52 -08:00
@ updatePaths ( ) if ( @ totalFramesDrawn % 4 ) is 0 or createjs . Ticker . getMeasuredFPS ( ) > createjs . Ticker . getFPS ( ) - 5
2014-02-28 17:12:23 -08:00
Backbone . Mediator . publish ( ' surface:ticked ' , { dt: 1 / @ options . frameRate } )
2014-01-03 10:32:13 -08:00
mib = @ stage . mouseInBounds
if @ mouseInBounds isnt mib
Backbone . Mediator . publish ( ' surface:mouse- ' + ( if mib then " over " else " out " ) , { } )
@mouseInBounds = mib
2014-02-28 11:27:32 -08:00
restoreWorldState: ->
2014-01-03 10:32:13 -08:00
@ world . getFrame ( @ getCurrentFrame ( ) ) . restoreState ( )
2014-02-17 11:53:52 -08:00
current = Math . max ( 0 , Math . min ( @ currentFrame , @ world . totalFrames - 1 ) )
if current - Math . floor ( current ) > 0.01
next = Math . ceil current
ratio = current % 1
@ world . frames [ next ] . restorePartialState ratio if next > 1
2014-01-03 10:32:13 -08:00
@ spriteBoss . updateSounds ( )
updateState: (frameChanged) ->
2014-02-28 11:27:32 -08:00
# world state must have been restored in @restoreWorldState
2014-01-03 10:32:13 -08:00
@ camera . updateZoom ( )
2014-02-24 14:40:28 -08:00
@ spriteBoss . update frameChanged unless @ casting
2014-01-03 10:32:13 -08:00
@ dimmer ? . setSprites @ spriteBoss . sprites
2014-02-17 11:53:52 -08:00
drawCurrentFrame: (e) ->
2014-01-03 10:32:13 -08:00
++ @ totalFramesDrawn
2014-02-17 11:53:52 -08:00
@ stage . update e
2014-01-03 10:32:13 -08:00
# paths - TODO: move to SpriteBoss? but only update on frame drawing instead of on every frame update?
updatePaths: ->
return unless @ options . paths
2014-02-24 14:40:28 -08:00
return if @ casting
2014-01-03 10:32:13 -08: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
selectedOnly = @ playing and @ world . showPaths is " selected "
@paths = @ trailmaster . generatePaths @ world , @ getCurrentFrame ( ) , selectedThang , @ spriteBoss . sprites , selectedOnly
@paths.name = ' paths '
@ spriteBoss . spriteLayers [ " Path " ] . addChild @ paths
hidePaths: ->
return if not @ paths
@ paths . parent . removeChild @ paths
@paths = null
# Screenshot
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 )
#console.log "Screenshot with scale", scale, "format", format, "quality", quality, "was", Math.floor(imageData.length / 1024), "kB"
screenshot = document . createElement ( " img " )
screenshot.src = imageData
#$('body').append(screenshot)
@ stage . uncache ( )
imageData