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 '
2014-07-10 17:22:08 -04:00
' level-set-debug ' : ' onSetDebug '
2014-01-03 13:32:13 -05:00
' level-highlight-sprites ' : ' onHighlightSprites '
' 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 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) ->
2014-05-16 20:18:56 -04:00
_ . find @ options . thangTypes , (m) -> m . get ( ' original ' ) is type or m . get ( ' name ' ) is type
2014-01-03 13:32:13 -05:00
createLayers: ->
@spriteLayers = { }
for [ name , priority ] in [
2014-06-30 22:16:26 -04:00
[ ' Land ' , - 40 ]
[ ' Ground ' , - 30 ]
[ ' Obstacle ' , - 20 ]
[ ' Path ' , - 10 ]
[ ' Default ' , 0 ]
[ ' Floating ' , 10 ]
2014-01-03 13:32:13 -05:00
]
@ 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
2014-06-30 22:16:26 -04:00
return @ spriteLayers [ ' Default ' ] unless child . layerPriority
2014-01-03 13:32:13 -05:00
layer = _ . findLast @ spriteLayers , (layer, name) ->
layer . layerPriority <= child . layerPriority
2014-06-30 22:16:26 -04:00
#console.log 'layer for', child, 'is', (layer ? @spriteLayers['Default'])
layer ? @ spriteLayers [ ' Default ' ]
2014-01-03 13:32:13 -05:00
addSprite: (sprite, id=null, layer=null) ->
id ? = sprite . thang . id
2014-06-30 22:16:26 -04:00
console . error ' Sprite collision! Already have: ' , id if @ sprites [ id ]
2014-01-03 13:32:13 -05:00
@ sprites [ id ] = sprite
2014-04-28 19:31:51 -04:00
@ spriteArray . push sprite
2014-06-30 22:16:26 -04:00
layer ? = @ spriteLayers [ ' Obstacle ' ] if sprite . thang ? . spriteName . search ( /(dungeon|indoor).wall/i ) isnt - 1
2014-05-14 18:59:56 -04:00
layer ? = @ layerForChild sprite . imageObject , sprite
layer . addChild sprite . imageObject
2014-01-03 13:32:13 -05:00
layer . updateLayerOrder ( )
sprite
createMarks: ->
2014-06-30 22:16:26 -04: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-06-30 22:16:26 -04: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-06-30 22:16:26 -04:00
@selfWizardSprite = @ createWizardSprite thangID: ' My Wizard ' , isSelf: true , sprites: @ sprites
2014-01-03 13:32:13 -05:00
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-06-30 22:16:26 -04:00
if not opponent . levelSlug or opponent . levelSlug is ' brawlwood '
2014-03-14 19:14:35 -04:00
sprite.targetPos = if opponent . team is ' ogres ' then { x: 52 , y: 52 } else { x: 28 , y: 28 }
2014-06-30 22:16:26 -04:00
else if opponent . levelSlug is ' dungeon-arena '
sprite.targetPos = if opponent . team is ' ogres ' then { x: 72 , y: 39 } else { x: 9 , y: 39 }
2014-07-08 21:51:10 -04:00
else if opponent . levelSlug is ' criss-cross '
sprite.targetPos = if opponent . team is ' ogres ' then { x: 50 , y: 12 } else { x: 0 , y: 40 }
2014-03-14 19:14:35 -04:00
else
2014-06-30 22:16:26 -04:00
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) ->
2014-06-30 22:16:26 -04:00
sprite = new WizardSprite @ thangTypeFor ( ' Wizard ' ) , @ createSpriteOptions ( options )
@ addSprite sprite , sprite . thang . id , @ spriteLayers [ ' Floating ' ]
2014-01-03 13:32:13 -05:00
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 ]
2014-05-02 20:03:30 -04:00
thangType = _ . find @ options . thangTypes , (m) ->
return false unless m . get ( ' actions ' ) or m . get ( ' raster ' )
return m . get ( ' name ' ) is thang . spriteName
thangType ? = _ . find @ options . thangTypes , (m) -> return m . get ( ' name ' ) is thang . spriteName
2014-05-09 15:56:58 -04:00
2014-02-17 20:38:49 -05:00
options = @ createSpriteOptions thang: thang
2014-05-15 20:07:53 -04:00
options.resolutionFactor = if thangType . get ( ' kind ' ) is ' Floor ' then 2 else SPRITE_RESOLUTION_FACTOR
2014-02-17 20:38:49 -05:00
sprite = new CocoSprite thangType , options
2014-05-16 18:33:49 -04:00
@ listenTo sprite , ' sprite:mouse-up ' , @ onSpriteMouseUp
2014-01-03 13:32:13 -05:00
@ addSprite sprite , null , layer
sprite . setDebug @ debug
sprite
removeSprite: (sprite) ->
2014-05-14 18:59:56 -04:00
sprite . imageObject . parent . removeChild sprite . imageObject
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-05-22 15:05:30 -04:00
@ stopListening sprite
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 ( )
2014-06-30 22:16:26 -04:00
@ spriteLayers [ ' Default ' ] . updateLayerOrder ( )
2014-01-03 13:32:13 -05:00
@ 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-06-30 22:16:26 -04:00
updateCache = updateCache or sprite . imageObject . parent is @ spriteLayers [ ' Obstacle ' ]
2014-01-03 13:32:13 -05:00
sprite . playSounds ( )
for thangID , sprite of @ sprites
missing = not ( sprite . notOfThisWorld or @ world . thangMap [ thangID ] ? . exists )
2014-06-30 22:16:26 -04:00
isObstacle = sprite . imageObject . parent is @ spriteLayers [ ' Obstacle ' ]
2014-01-03 13:32:13 -05:00
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-05-22 17:16:39 -04:00
return if _ . any ( s . stillLoading for s in wallSprites )
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()
2014-06-30 22:16:26 -04:00
@ spriteLayers [ ' Obstacle ' ] . uncache ( ) if @ spriteLayers [ ' Obstacle ' ] . cacheID # might have changed sizes
@ spriteLayers [ ' Obstacle ' ] . cache ( )
2014-01-03 13:32:13 -05:00
# test performance of doing land layer, too, to see if it's faster
2014-06-30 22:16:26 -04:00
# @spriteLayers['Land'].uncache() if @spriteLayers['Land'].cacheID # might have changed sizes
# @spriteLayers['Land'].cache()
2014-05-15 20:07:53 -04:00
# I don't notice much difference - Scott
2014-01-03 13:32:13 -05:00
@cached = true
spriteFor: (thangID) -> @ sprites [ thangID ]
2014-02-06 17:00:27 -05:00
onNewWorld: (e) ->
@world = @options.world = e . world
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
2014-05-14 18:29:55 -04:00
worldPos ? = @ camera . screenToWorld { x: e . originalEvent . rawX , y: e . originalEvent . rawY } if e
2014-06-10 18:09:56 -04:00
if worldPos and ( @ options . navigateToSelection or not sprite or treemaThangSelected ) and e ? . originalEvent ? . nativeEvent ? . which isnt 3
2014-05-14 18:59:56 -04:00
@ camera . zoomTo ( sprite ? . imageObject or @ camera . worldToSurface ( worldPos ) , @ camera . zoom , 1000 , true )
2014-01-03 13:32:13 -05:00
sprite = null if @ options . choosing # Don't select sprites while choosing
if sprite isnt @ selectedSprite
@ selectedSprite ? . selected = false
sprite ? . selected = true
@selectedSprite = sprite
2014-05-16 13:03:45 -04:00
alive = sprite and not ( 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-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