2014-01-03 13:32:13 -05:00
CocoClass = require ' lib/CocoClass '
{ me } = require ' lib/auth '
Layer = require ' ./Layer '
IndieSprite = require ' lib/surface/IndieSprite '
WizardSprite = require ' lib/surface/WizardSprite '
CocoSprite = require ' lib/surface/CocoSprite '
Mark = require ' ./Mark '
Grid = require ' lib/world/Grid '
module.exports = class SpriteBoss extends CocoClass
subscriptions:
' bus:player-joined ' : ' onPlayerJoined '
' bus:player-left ' : ' onPlayerLeft '
' level-set-debug ' : ' onSetDebug '
' level-highlight-sprites ' : ' onHighlightSprites '
2014-02-24 22:49:05 -05:00
' sprite:mouse-up ' : ' onSpriteMouseUp '
2014-01-03 13:32:13 -05:00
' surface:stage-mouse-down ' : ' onStageMouseDown '
' level-select-sprite ' : ' onSelectSprite '
' level-suppress-selection-sounds ' : ' onSuppressSelectionSounds '
' level-lock-select ' : ' onSetLockSelect '
' level:restarted ' : ' onLevelRestarted '
2014-02-06 17:00:27 -05:00
' god:new-world-created ' : ' onNewWorld '
2014-02-24 17:40:28 -05:00
' tome:cast-spells ' : ' onCastSpells '
2014-02-24 22:49:05 -05:00
' camera:dragged ' : ' onCameraDragged '
2014-03-12 13:08:57 -04:00
' sprite:loaded ' : -> @ update ( true )
2014-01-03 13:32:13 -05:00
constructor: (@options) ->
super ( )
2014-02-26 21:42:39 -05:00
@dragged = 0
2014-01-03 13:32:13 -05:00
@ options ? = { }
@camera = @ options . camera
@surfaceLayer = @ options . surfaceLayer
@surfaceTextLayer = @ options . surfaceTextLayer
@world = options . world
@ options . thangTypes ? = [ ]
@sprites = { }
2014-04-28 19:31:51 -04:00
@spriteArray = [ ] # Mirror @sprites, but faster for when we just need to iterate
2014-01-03 13:32:13 -05:00
@selfWizardSprite = null
@ createLayers ( )
@spriteSheetCache = { }
destroy: ->
2014-04-28 20:06:43 -04:00
@ removeSprite sprite for thangID , sprite of @ sprites
2014-01-03 13:32:13 -05:00
@ targetMark ? . destroy ( )
@ selectionMark ? . destroy ( )
2014-02-14 13:57:47 -05:00
super ( )
2014-01-03 13:32:13 -05:00
2014-04-28 19:31:51 -04:00
toString: -> " <SpriteBoss: #{ @ spriteArray . length } sprites> "
2014-01-03 13:32:13 -05:00
thangTypeFor: (type) ->
_ . find @ options . thangTypes , (m) -> m . get ( ' original ' ) is type or m . get ( ' name ' ) is type
createLayers: ->
@spriteLayers = { }
for [ name , priority ] in [
[ " Land " , - 40 ]
[ " Ground " , - 30 ]
[ " Obstacle " , - 20 ]
[ " Path " , - 10 ]
[ " Default " , 0 ]
[ " Floating " , 10 ]
]
@ spriteLayers [ name ] = new Layer name: name , layerPriority: priority , transform: Layer . TRANSFORM_CHILD , camera: @ camera
@ surfaceLayer . addChild _ . values ( @ spriteLayers ) . . .
layerForChild: (child, sprite) ->
unless child . layerPriority ?
# TODO: make better system
child.layerPriority = 0 if sprite ? . thang ? . isSelectable
child.layerPriority = - 40 if sprite ? . thang ? . isLand
return @ spriteLayers [ " Default " ] unless child . layerPriority
layer = _ . findLast @ spriteLayers , (layer, name) ->
layer . layerPriority <= child . layerPriority
#console.log "layer for", child, "is", (layer ? @spriteLayers["Default"])
layer ? @ spriteLayers [ " Default " ]
addSprite: (sprite, id=null, layer=null) ->
id ? = sprite . thang . id
console . error " Sprite collision! Already have: " , id if @ sprites [ id ]
@ sprites [ id ] = sprite
2014-04-28 19:31:51 -04:00
@ spriteArray . push sprite
2014-01-30 17:21:58 -05:00
layer ? = @ spriteLayers [ " Obstacle " ] if sprite . thang ? . spriteName . search ( /(dungeon|indoor).wall/i ) isnt - 1
2014-01-03 13:32:13 -05:00
layer ? = @ layerForChild sprite . displayObject , sprite
layer . addChild sprite . displayObject
layer . updateLayerOrder ( )
sprite
createMarks: ->
2014-03-06 18:52:09 -05:00
@targetMark = new Mark name: ' target ' , camera: @ camera , layer: @ spriteLayers [ " Ground " ] , thangType: ' target '
@selectionMark = new Mark name: ' selection ' , camera: @ camera , layer: @ spriteLayers [ " Ground " ] , thangType: ' selection '
2014-01-03 13:32:13 -05:00
createSpriteOptions: (options) ->
2014-03-06 18:52:09 -05:00
_ . extend options , camera: @ camera , resolutionFactor: 4 , groundLayer: @ spriteLayers [ " Ground " ] , textLayer: @ surfaceTextLayer , floatingLayer: @ spriteLayers [ " Floating " ] , spriteSheetCache: @ spriteSheetCache , showInvisible: @ options . showInvisible
2014-01-03 13:32:13 -05:00
createIndieSprites: (indieSprites, withWizards) ->
unless @ indieSprites
2014-01-06 20:11:57 -05:00
@indieSprites = [ ]
@indieSprites = ( @ createIndieSprite indieSprite for indieSprite in indieSprites ) if indieSprites
2014-05-01 17:36:26 -04:00
if withWizards and not @ selfWizardSprite
2014-01-03 13:32:13 -05:00
@selfWizardSprite = @ createWizardSprite thangID: " My Wizard " , isSelf: true , sprites: @ sprites
createIndieSprite: (indieSprite) ->
unless thangType = @ thangTypeFor indieSprite . thangType
console . warn " Need to convert #{ indieSprite . id } ' s ThangType #{ indieSprite . thangType } to a ThangType reference. Until then, #{ indieSprite . id } won ' t show up. "
return
2014-02-19 16:43:44 -05:00
sprite = new IndieSprite thangType , @ createSpriteOptions { thangID: indieSprite . id , pos: indieSprite . pos , sprites: @ sprites , colorConfig: indieSprite . colorConfig }
2014-01-03 13:32:13 -05:00
@ addSprite sprite , sprite . thang . id
2014-02-22 15:01:05 -05:00
createOpponentWizard: (opponent) ->
# TODO: colorize name and cloud by team, colorize wizard by user's color config, level-specific wizard spawn points
sprite = @ createWizardSprite thangID: opponent . id , name: opponent . name
2014-03-14 19:14:35 -04:00
if not opponent . levelSlug or opponent . levelSlug is " brawlwood "
sprite.targetPos = if opponent . team is ' ogres ' then { x: 52 , y: 52 } else { x: 28 , y: 28 }
else if opponent . levelSlug is " dungeon-arena "
sprite.targetPos = if opponent . team is ' ogres ' then { x : 72 , y: 39 } else { x: 9 , y : 39 }
else
sprite.targetPos = if opponent . team is ' ogres ' then { x : 52 , y: 28 } else { x: 20 , y : 28 }
2014-03-14 20:15:07 -04:00
2014-01-03 13:32:13 -05:00
createWizardSprite: (options) ->
sprite = new WizardSprite @ thangTypeFor ( " Wizard " ) , @ createSpriteOptions ( options )
@ addSprite sprite , sprite . thang . id , @ spriteLayers [ " Floating " ]
onPlayerJoined: (e) ->
# Create another WizardSprite, unless this player is just me
pid = e . player . id
return if pid is me . id
wiz = @ createWizardSprite thangID: pid , sprites: @ sprites
wiz . animateIn ( )
state = e . player . wizard or { }
wiz . setInitialState ( state . targetPos , @ sprites [ state . targetSprite ] )
onPlayerLeft: (e) ->
pid = e . player . id
@ sprites [ pid ] ? . animateOut => @ removeSprite @ sprites [ pid ]
onSetDebug: (e) ->
return if e . debug is @ debug
@debug = e . debug
2014-04-28 19:31:51 -04:00
sprite . setDebug @ debug for sprite in @ spriteArray
2014-01-03 13:32:13 -05:00
onHighlightSprites: (e) ->
highlightedIDs = e . thangIDs or [ ]
for thangID , sprite of @ sprites
sprite . setHighlight ? thangID in highlightedIDs , e . delay
addThangToSprites: (thang, layer=null) ->
return console . warn ' Tried to add Thang to the surface it already has: ' , thang . id if @ sprites [ thang . id ]
thangType = _ . find @ options . thangTypes , (m) -> m . get ( ' name ' ) is thang . spriteName
2014-02-17 20:38:49 -05:00
options = @ createSpriteOptions thang: thang
options.resolutionFactor = if thangType . get ( ' kind ' ) is ' Floor ' then 2 else 4
sprite = new CocoSprite thangType , options
2014-01-03 13:32:13 -05:00
@ addSprite sprite , null , layer
sprite . setDebug @ debug
sprite
removeSprite: (sprite) ->
sprite . displayObject . parent . removeChild sprite . displayObject
2014-03-13 18:35:28 -04:00
thang = sprite . thang
2014-01-03 13:32:13 -05:00
delete @ sprites [ sprite . thang . id ]
2014-04-28 19:31:51 -04:00
@ spriteArray . splice @ spriteArray . indexOf ( sprite ) , 1
2014-02-14 16:31:26 -05:00
sprite . destroy ( )
2014-03-13 18:35:28 -04:00
sprite.thang = thang # Keep around so that we know which thang the destroyed thang was for
2014-01-03 13:32:13 -05:00
updateSounds: ->
2014-04-28 19:31:51 -04:00
sprite . playSounds ( ) for sprite in @ spriteArray # hmm; doesn't work for sprites which we didn't add yet in adjustSpriteExistence
2014-01-03 13:32:13 -05:00
update: (frameChanged) ->
@ adjustSpriteExistence ( ) if frameChanged
2014-04-28 19:31:51 -04:00
sprite . update frameChanged for sprite in @ spriteArray
2014-01-03 13:32:13 -05:00
@ updateSelection ( )
@ spriteLayers [ " Default " ] . updateLayerOrder ( )
@ cache ( )
adjustSpriteExistence: ->
# Add anything new, remove anything old, update everything current
updateCache = false
for thang in @ world . thangs when thang . exists
if sprite = @ sprites [ thang . id ]
sprite . setThang thang # make sure Sprite has latest Thang
else
2014-01-29 13:14:12 -05:00
sprite = @ addThangToSprites ( thang )
Backbone . Mediator . publish ' surface:new-thang-added ' , thang : thang , sprite : sprite
2014-01-03 13:32:13 -05:00
updateCache = updateCache or sprite . displayObject . parent is @ spriteLayers [ " Obstacle " ]
sprite . playSounds ( )
for thangID , sprite of @ sprites
missing = not ( sprite . notOfThisWorld or @ world . thangMap [ thangID ] ? . exists )
isObstacle = sprite . displayObject . parent is @ spriteLayers [ " Obstacle " ]
updateCache = updateCache or ( isObstacle and ( missing or sprite . hasMoved ) )
sprite.hasMoved = false
@ removeSprite sprite if missing
@ cache true if updateCache and @ cached
2014-03-12 12:10:36 -04:00
2014-02-25 14:59:23 -05:00
# mainly for handling selecting thangs from session when the thang is not always in existence
if @ willSelectThang and @ sprites [ @ willSelectThang [ 0 ] ]
@ selectThang @ willSelectThang . . .
2014-01-03 13:32:13 -05:00
cache: (update=false) ->
return if @ cached and not update
2014-04-28 19:31:51 -04:00
wallSprites = ( sprite for sprite in @ spriteArray when sprite . thangType ? . get ( ' name ' ) . search ( /(dungeon|indoor).wall/i ) isnt - 1 )
2014-01-03 13:32:13 -05:00
walls = ( sprite . thang for sprite in wallSprites )
@ world . calculateBounds ( )
2014-01-15 16:04:48 -05:00
wallGrid = new Grid walls , @ world . size ( ) . . .
2014-01-03 13:32:13 -05:00
for wallSprite in wallSprites
2014-01-15 16:04:48 -05:00
wallSprite . updateActionDirection wallGrid
2014-01-03 13:32:13 -05:00
wallSprite . updateScale ( )
wallSprite . updatePosition ( )
#console.log @wallGrid.toString()
@ spriteLayers [ " Obstacle " ] . uncache ( ) if @ spriteLayers [ " Obstacle " ] . cacheID # might have changed sizes
@ spriteLayers [ " Obstacle " ] . cache ( )
# test performance of doing land layer, too, to see if it's faster
#@spriteLayers["Land"].uncache() if @spriteLayers["Land"].cacheID # might have changed sizes
#@spriteLayers["Land"].cache()
@cached = true
spriteFor: (thangID) -> @ sprites [ thangID ]
2014-02-06 17:00:27 -05:00
onNewWorld: (e) ->
@world = @options.world = e . world
2014-02-25 20:14:39 -05:00
@ play ( )
onCastSpells: -> @ stop ( )
2014-03-12 12:10:36 -04:00
2014-02-25 20:14:39 -05:00
play: ->
2014-05-06 14:02:53 -04:00
sprite . play ( ) for sprite in @ spriteArray
2014-02-24 17:40:28 -05:00
@ selectionMark ? . play ( )
@ targetMark ? . play ( )
2014-03-12 12:10:36 -04:00
2014-02-25 20:14:39 -05:00
stop: ->
2014-05-06 14:02:53 -04:00
sprite . stop ( ) for sprite in @ spriteArray
2014-02-24 17:40:28 -05:00
@ selectionMark ? . stop ( )
@ targetMark ? . stop ( )
2014-02-06 17:00:27 -05:00
2014-01-03 13:32:13 -05:00
# Selection
onSuppressSelectionSounds: (e) -> @suppressSelectionSounds = e . suppress
onSetLockSelect: (e) -> @selectLocked = e . lock
onLevelRestarted: (e) ->
@selectLocked = false
@ selectSprite e , null
onSelectSprite: (e) ->
@ selectThang e . thangID , e . spellName
2014-02-24 22:49:05 -05:00
onCameraDragged: ->
2014-02-26 21:42:39 -05:00
@ dragged += 1
2014-02-24 22:49:05 -05:00
onSpriteMouseUp: (e) ->
2014-03-12 20:50:59 -04:00
return if key . shift #and @options.choosing
2014-02-26 21:42:39 -05:00
return @dragged = 0 if @ dragged > 3
@dragged = 0
2014-01-03 13:32:13 -05:00
sprite = if e . sprite ? . thang ? . isSelectable then e . sprite else null
@ selectSprite e , sprite
onStageMouseDown: (e) ->
2014-03-12 20:50:59 -04:00
return if key . shift #and @options.choosing
2014-01-03 13:32:13 -05:00
@ selectSprite e if e . onBackground
2014-03-14 16:27:29 -04:00
selectThang: (thangID, spellName=null, treemaThangSelected = null) ->
2014-02-25 14:59:23 -05:00
return @willSelectThang = [ thangID , spellName ] unless @ sprites [ thangID ]
2014-03-14 16:27:29 -04:00
@ selectSprite null , @ sprites [ thangID ] , spellName , treemaThangSelected
2014-01-03 13:32:13 -05:00
2014-03-14 16:27:29 -04:00
selectSprite: (e, sprite=null, spellName=null, treemaThangSelected = null) ->
2014-01-03 13:32:13 -05:00
return if e and ( @ disabled or @ selectLocked ) # Ignore clicks for selection/panning/wizard movement while disabled or select is locked
worldPos = sprite ? . thang ? . pos
worldPos ? = @ camera . canvasToWorld { x: e . originalEvent . rawX , y: e . originalEvent . rawY } if e
2014-03-14 16:27:29 -04:00
if worldPos and ( @ options . navigateToSelection or not sprite or treemaThangSelected )
2014-01-03 13:32:13 -05:00
@ camera . zoomTo ( sprite ? . displayObject or @ camera . worldToSurface ( worldPos ) , @ camera . zoom , 1000 )
sprite = null if @ options . choosing # Don't select sprites while choosing
if sprite isnt @ selectedSprite
@ selectedSprite ? . selected = false
sprite ? . selected = true
@selectedSprite = sprite
alive = sprite ? . thang . health > 0
2014-01-09 14:04:22 -05:00
2014-01-21 00:19:54 -05:00
Backbone . Mediator . publish ' surface:sprite-selected ' ,
2014-01-03 13:32:13 -05:00
thang: if sprite then sprite . thang else null
sprite: sprite
spellName: spellName ? e ? . spellName
originalEvent: e
worldPos: worldPos
2014-03-13 18:35:28 -04:00
@willSelectThang = null if sprite # Now that we've done a real selection, don't reselect some other Thang later.
2014-01-09 14:04:22 -05:00
if alive and not @ suppressSelectionSounds
instance = sprite . playSound ' selected '
2014-01-09 17:04:46 -05:00
if instance ? . playState is ' playSucceeded '
2014-01-09 14:04:22 -05:00
Backbone . Mediator . publish ' thang-began-talking ' , thang: sprite ? . thang
instance . addEventListener ' complete ' , ->
Backbone . Mediator . publish ' thang-finished-talking ' , thang: sprite ? . thang
2014-03-13 18:35:28 -04:00
2014-01-03 13:32:13 -05:00
# Marks
updateSelection: ->
2014-02-14 18:35:54 -05:00
if @ selectedSprite ? . thang and ( not @ selectedSprite . thang . exists or not @ world . getThangByID @ selectedSprite . thang . id )
2014-03-13 18:35:28 -04:00
thangID = @ selectedSprite . thang . id
@selectedSprite = null # Don't actually trigger deselection, but remove the selected sprite.
2014-02-16 21:29:24 -05:00
@ selectionMark ? . toggle false
2014-03-13 18:35:28 -04:00
@willSelectThang = [ thangID , null ]
2014-01-03 13:32:13 -05:00
@ updateTarget ( )
return unless @ selectionMark
2014-03-14 20:15:07 -04:00
@selectedSprite = null if @ selectedSprite and ( @ selectedSprite . destroyed or not @ selectedSprite . thang )
2014-01-03 13:32:13 -05:00
@ selectionMark . toggle @ selectedSprite ?
@ selectionMark . setSprite @ selectedSprite
@ selectionMark . update ( )
updateTarget: ->
return unless @ targetMark
thang = @ selectedSprite ? . thang
target = thang ? . target
targetPos = thang ? . targetPos
targetPos = null if targetPos ? . isZero ? ( ) # Null targetPos get serialized as (0, 0, 0)
@ targetMark . setSprite if target then @ sprites [ target . id ] else null
2014-01-21 12:03:04 -05:00
@ targetMark . toggle @ targetMark . sprite or targetPos
2014-01-03 13:32:13 -05:00
@ targetMark . update if targetPos then @ camera . worldToSurface targetPos else null