2014-11-28 20:49:41 -05:00
CocoClass = require ' core/CocoClass '
{ me } = require ' core/auth '
2014-09-28 17:00:48 -04:00
LayerAdapter = require ' ./LayerAdapter '
IndieLank = require ' lib/surface/IndieLank '
WizardLank = require ' lib/surface/WizardLank '
FlagLank = require ' lib/surface/FlagLank '
Lank = require ' lib/surface/Lank '
Mark = require ' ./Mark '
Grid = require ' lib/world/Grid '
2015-02-06 19:54:45 -05:00
utils = require ' core/utils '
2014-09-28 17:00:48 -04:00
module.exports = class LankBoss extends CocoClass
subscriptions:
' bus:player-joined ' : ' onPlayerJoined '
' bus:player-left ' : ' onPlayerLeft '
' level:set-debug ' : ' onSetDebug '
' sprite:highlight-sprites ' : ' onHighlightSprites '
' surface:stage-mouse-down ' : ' onStageMouseDown '
' level:select-sprite ' : ' onSelectSprite '
' level:suppress-selection-sounds ' : ' onSuppressSelectionSounds '
' level:lock-select ' : ' onSetLockSelect '
' level:restarted ' : ' onLevelRestarted '
' god:new-world-created ' : ' onNewWorld '
' god:streaming-world-updated ' : ' onNewWorld '
' camera:dragged ' : ' onCameraDragged '
' sprite:loaded ' : -> @ update ( true )
' level:flag-color-selected ' : ' onFlagColorSelected '
' level:flag-updated ' : ' onFlagUpdated '
' surface:flag-appeared ' : ' onFlagAppeared '
' surface:remove-selected-flag ' : ' onRemoveSelectedFlag '
constructor: (@options) ->
super ( )
@dragged = 0
@ options ? = { }
@camera = @ options . camera
@webGLStage = @ options . webGLStage
@surfaceTextLayer = @ options . surfaceTextLayer
2015-01-30 10:51:57 -05:00
@world = @ options . world
2014-09-28 17:00:48 -04:00
@ options . thangTypes ? = [ ]
@lanks = { }
@lankArray = [ ] # Mirror @lanks, but faster for when we just need to iterate
@selfWizardLank = null
@ createLayers ( )
@pendingFlags = [ ]
destroy: ->
@ removeLank lank for thangID , lank of @ lanks
@ targetMark ? . destroy ( )
@ selectionMark ? . destroy ( )
2014-10-06 18:04:33 -04:00
lankLayer . destroy ( ) for lankLayer in _ . values @ layerAdapters
2014-09-28 17:00:48 -04:00
super ( )
toString: -> " <LankBoss: #{ @ lankArray . length } lanks> "
thangTypeFor: (type) ->
_ . find @ options . thangTypes , (m) -> m . get ( ' original ' ) is type or m . get ( ' name ' ) is type
createLayers: ->
@layerAdapters = { }
for [ name , priority ] in [
[ ' Land ' , - 40 ]
[ ' Ground ' , - 30 ]
[ ' Obstacle ' , - 20 ]
[ ' Path ' , - 10 ]
[ ' Default ' , 0 ]
[ ' Floating ' , 10 ]
]
@ layerAdapters [ name ] = new LayerAdapter name: name , webGL: true , layerPriority: priority , transform: LayerAdapter . TRANSFORM_SURFACE , camera: @ camera
@ webGLStage . addChild ( lankLayer . container for lankLayer in _ . values ( @ layerAdapters ) ) . . .
layerForChild: (child, lank) ->
unless child . layerPriority ?
if thang = lank ? . thang
child.layerPriority = thang . layerPriority
child . layerPriority ? = 0 if thang . isSelectable
child . layerPriority ? = - 40 if thang . isLand
child . layerPriority ? = 0
return @ layerAdapters [ ' Default ' ] unless child . layerPriority
layer = _ . findLast @ layerAdapters , (layer, name) ->
layer . layerPriority <= child . layerPriority
layer ? = @ layerAdapters [ ' Land ' ] if child . layerPriority < - 40
layer ? @ layerAdapters [ ' Default ' ]
addLank: (lank, id=null, layer=null) ->
id ? = lank . thang . id
console . error ' Lank collision! Already have: ' , id if @ lanks [ id ]
@ lanks [ id ] = lank
@ lankArray . push lank
layer ? = @ layerAdapters [ ' Obstacle ' ] if lank . thang ? . spriteName . search ( /(dungeon|indoor).wall/i ) isnt - 1
layer ? = @ layerForChild lank . sprite , lank
layer . addLank lank
layer . updateLayerOrder ( )
lank
createMarks: ->
@targetMark = new Mark name: ' target ' , camera: @ camera , layer: @ layerAdapters [ ' Ground ' ] , thangType: ' target '
@selectionMark = new Mark name: ' selection ' , camera: @ camera , layer: @ layerAdapters [ ' Ground ' ] , thangType: ' selection '
createLankOptions: (options) ->
_ . extend options , camera: @ camera , resolutionFactor: SPRITE_RESOLUTION_FACTOR , groundLayer: @ layerAdapters [ ' Ground ' ] , textLayer: @ surfaceTextLayer , floatingLayer: @ layerAdapters [ ' Floating ' ] , showInvisible: @ options . showInvisible
createIndieLanks: (indieLanks, withWizards) ->
unless @ indieLanks
@indieLanks = [ ]
@indieLanks = ( @ createIndieLank indieLank for indieLank in indieLanks ) if indieLanks
if withWizards and not @ selfWizardLank
@selfWizardLank = @ createWizardLank thangID: ' My Wizard ' , isSelf: true , lanks: @ lanks
createIndieLank: (indieLank) ->
unless thangType = @ thangTypeFor indieLank . thangType
console . warn " Need to convert #{ indieLank . id } ' s ThangType #{ indieLank . thangType } to a ThangType reference. Until then, #{ indieLank . id } won ' t show up. "
return
lank = new IndieLank thangType , @ createLankOptions { thangID: indieLank . id , pos: indieLank . pos , lanks: @ lanks , team: indieLank . team , teamColors: @ world . getTeamColors ( ) }
@ addLank lank , lank . thang . id
createOpponentWizard: (opponent) ->
# TODO: colorize name and cloud by team, colorize wizard by user's color config, level-specific wizard spawn points
lank = @ createWizardLank thangID: opponent . id , name: opponent . name , codeLanguage: opponent . codeLanguage
if not opponent . levelSlug or opponent . levelSlug is ' brawlwood '
lank.targetPos = if opponent . team is ' ogres ' then { x: 52 , y: 52 } else { x: 28 , y: 28 }
else if opponent . levelSlug in [ ' dungeon-arena ' , ' sky-span ' ]
lank.targetPos = if opponent . team is ' ogres ' then { x: 72 , y: 39 } else { x: 9 , y: 39 }
else if opponent . levelSlug is ' criss-cross '
lank.targetPos = if opponent . team is ' ogres ' then { x: 50 , y: 12 } else { x: 0 , y: 40 }
else
lank.targetPos = if opponent . team is ' ogres ' then { x: 52 , y: 28 } else { x: 20 , y: 28 }
createWizardLank: (options) ->
lank = new WizardLank @ thangTypeFor ( ' Wizard ' ) , @ createLankOptions ( options )
@ addLank lank , lank . thang . id , @ layerAdapters [ ' Floating ' ]
onPlayerJoined: (e) ->
# Create another WizardLank, unless this player is just me
pid = e . player . id
return if pid is me . id
wiz = @ createWizardLank thangID: pid , lanks: @ lanks
wiz . animateIn ( )
state = e . player . wizard or { }
wiz . setInitialState ( state . targetPos , @ lanks [ state . targetLank ] )
onPlayerLeft: (e) ->
pid = e . player . id
@ lanks [ pid ] ? . animateOut => @ removeLank @ lanks [ pid ]
onSetDebug: (e) ->
return if e . debug is @ debug
@debug = e . debug
lank . setDebug @ debug for lank in @ lankArray
onHighlightSprites: (e) ->
highlightedIDs = e . thangIDs or [ ]
for thangID , lank of @ lanks
lank . setHighlight ? thangID in highlightedIDs , e . delay
addThangToLanks: (thang, layer=null) ->
return console . warn ' Tried to add Thang to the surface it already has: ' , thang . id if @ lanks [ thang . id ]
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
return console . error " Couldn ' t find ThangType for " , thang unless thangType
options = @ createLankOptions thang: thang
options.resolutionFactor = if thangType . get ( ' kind ' ) is ' Floor ' then 2 else SPRITE_RESOLUTION_FACTOR
2015-02-12 22:47:57 -05:00
if @ options . playerNames and /Hero Placeholder/ . test thang . id
options.playerName = @ options . playerNames [ thang . team ]
2014-09-28 17:00:48 -04:00
lank = new Lank thangType , options
@ listenTo lank , ' sprite:mouse-up ' , @ onLankMouseUp
@ addLank lank , null , layer
lank . setDebug @ debug
lank
removeLank: (lank) ->
lank . layer . removeLank ( lank )
thang = lank . thang
delete @ lanks [ lank . thang . id ]
@ lankArray . splice @ lankArray . indexOf ( lank ) , 1
@ stopListening lank
lank . destroy ( )
lank.thang = thang # Keep around so that we know which thang the destroyed thang was for
updateSounds: ->
lank . playSounds ( ) for lank in @ lankArray # hmm; doesn't work for lanks which we didn't add yet in adjustLankExistence
update: (frameChanged) ->
@ adjustLankExistence ( ) if frameChanged
lank . update frameChanged for lank in @ lankArray
@ updateSelection ( )
@ layerAdapters [ ' Default ' ] . updateLayerOrder ( )
@ cacheObstacles ( )
adjustLankExistence: ->
# Add anything new, remove anything old, update everything current
updatedObstacles = [ ]
itemsJustEquipped = [ ]
for thang in @ world . thangs when thang . exists and thang . pos
2014-11-29 16:09:38 -05:00
itemsJustEquipped = itemsJustEquipped . concat @ equipNewItems thang if thang . equip
2014-09-28 17:00:48 -04:00
if lank = @ lanks [ thang . id ]
lank . setThang thang # make sure Lank has latest Thang
else
lank = @ addThangToLanks ( thang )
Backbone . Mediator . publish ' surface:new-thang-added ' , thang: thang , sprite: lank
updatedObstacles . push lank if lank . sprite . parent is @ layerAdapters [ ' Obstacle ' ]
lank . playSounds ( )
item . modifyStats ( ) for item in itemsJustEquipped
for thangID , lank of @ lanks
missing = not ( lank . notOfThisWorld or @ world . thangMap [ thangID ] ? . exists )
isObstacle = lank . sprite . parent is @ layerAdapters [ ' Obstacle ' ]
updatedObstacles . push lank if isObstacle and ( missing or lank . hasMoved )
lank.hasMoved = false
@ removeLank lank if missing
@ cacheObstacles updatedObstacles if updatedObstacles . length and @ cachedObstacles
# mainly for handling selecting thangs from session when the thang is not always in existence
if @ willSelectThang and @ lanks [ @ willSelectThang [ 0 ] ]
@ selectThang @ willSelectThang . . .
2015-02-06 19:54:45 -05:00
@ updateScreenReader ( )
updateScreenReader: ->
# Testing ASCII map for screen readers
return unless me . get ( ' name ' ) is ' zersiax ' #in ['zersiax', 'Nick']
ascii = $ ( ' # ascii-surface ' )
thangs = ( lank . thang for lank in @ lankArray )
grid = new Grid thangs , @ world . width , @ world . height , 0 , 0 , 0 , true
utils . replaceText ascii , grid . toString true
ascii . css ' transform ' , ' initial '
fullWidth = ascii . innerWidth ( )
fullHeight = ascii . innerHeight ( )
availableWidth = ascii . parent ( ) . innerWidth ( )
availableHeight = ascii . parent ( ) . innerHeight ( )
scale = availableWidth / fullWidth
scale = Math . min scale , availableHeight / fullHeight
ascii . css ' transform ' , " scale( #{ scale } ) "
2014-09-28 17:00:48 -04:00
equipNewItems: (thang) ->
itemsJustEquipped = [ ]
if thang . equip and not thang . equipped
thang . equip ( ) # Pretty hacky, but needed since initialize may not be called if we're not running Systems.
itemsJustEquipped . push thang
if thang . inventoryIDs
# Even hackier: these items were only created/equipped during simulation, so we reequip here.
for slot , itemID of thang . inventoryIDs
item = @ world . getThangByID itemID
unless item . equipped
console . log thang . id , ' equipping ' , item , ' in ' , thang . slot , ' Surface-side, but it cannot equip? ' unless item . equip
2014-11-20 14:59:49 -05:00
item . equip ? ( )
itemsJustEquipped . push item if item . equip
2014-09-28 17:00:48 -04:00
return itemsJustEquipped
cacheObstacles: (updatedObstacles=null) ->
return if @ cachedObstacles and not updatedObstacles
2014-11-09 19:19:18 -05:00
lankArray = @ lankArray
wallLanks = ( lank for lank in lankArray when lank . thangType ? . get ( ' name ' ) . search ( /(dungeon|indoor).wall/i ) isnt - 1 )
2014-09-28 17:00:48 -04:00
return if _ . any ( s . stillLoading for s in wallLanks )
walls = ( lank . thang for lank in wallLanks )
@ world . calculateBounds ( )
2014-11-09 19:19:18 -05:00
wallGrid = new Grid walls , @ world . width , @ world . height
2014-09-28 17:00:48 -04:00
if updatedObstacles
possiblyUpdatedWallLanks = ( lank for lank in wallLanks when _ . find updatedObstacles , (w2) -> lank is w2 or ( Math . abs ( lank . thang . pos . x - w2 . thang . pos . x ) + Math . abs ( lank . thang . pos . y - w2 . thang . pos . y ) ) <= 16 )
else
possiblyUpdatedWallLanks = wallLanks
2014-10-03 14:21:05 -04:00
# console.log 'updating up to', possiblyUpdatedWallLanks.length, 'of', wallLanks.length, 'wall lanks from updatedObstacles', updatedObstacles
2014-09-28 17:00:48 -04:00
for wallLank in possiblyUpdatedWallLanks
2014-10-03 14:21:05 -04:00
wallLank . queueAction ' idle ' if not wallLank . currentRootAction
wallLank . lockAction ( false )
2014-09-28 17:00:48 -04:00
wallLank . updateActionDirection wallGrid
2014-10-03 14:21:05 -04:00
wallLank . lockAction ( true )
2014-09-28 17:00:48 -04:00
wallLank . updateScale ( )
wallLank . updatePosition ( )
2014-10-03 14:21:05 -04:00
# console.log wallGrid.toString()
2014-09-28 17:00:48 -04:00
@cachedObstacles = true
2014-10-06 18:04:33 -04:00
2014-09-28 17:00:48 -04:00
lankFor: (thangID) -> @ lanks [ thangID ]
onNewWorld: (e) ->
@world = @options.world = e . world
play: ->
lank . play ( ) for lank in @ lankArray
@ selectionMark ? . play ( )
@ targetMark ? . play ( )
stop: ->
lank . stop ( ) for lank in @ lankArray
@ selectionMark ? . stop ( )
@ targetMark ? . stop ( )
# Selection
onSuppressSelectionSounds: (e) -> @suppressSelectionSounds = e . suppress
onSetLockSelect: (e) -> @selectLocked = e . lock
onLevelRestarted: (e) ->
@selectLocked = false
@ selectLank e , null
onSelectSprite: (e) ->
@ selectThang e . thangID , e . spellName
onCameraDragged: ->
@ dragged += 1
onLankMouseUp: (e) ->
return if key . shift #and @options.choosing
return @dragged = 0 if @ dragged > 3
@dragged = 0
lank = if e . sprite ? . thang ? . isSelectable then e . sprite else null
return if @ flagCursorLank and lank ? . thangType . get ( ' name ' ) is ' Flag '
@ selectLank e , lank
onStageMouseDown: (e) ->
return if key . shift #and @options.choosing
@ selectLank e if e . onBackground
selectThang: (thangID, spellName=null, treemaThangSelected = null) ->
return @willSelectThang = [ thangID , spellName ] unless @ lanks [ thangID ]
@ selectLank null , @ lanks [ thangID ] , spellName , treemaThangSelected
selectLank: (e, lank=null, spellName=null, treemaThangSelected = null) ->
return if e and ( @ disabled or @ selectLocked ) # Ignore clicks for selection/panning/wizard movement while disabled or select is locked
worldPos = lank ? . thang ? . pos
worldPos ? = @ camera . screenToWorld { x: e . originalEvent . rawX , y: e . originalEvent . rawY } if e ? . originalEvent
2014-10-03 14:54:06 -04:00
if ( not @ reallyStopMoving ) and worldPos and ( @ options . navigateToSelection or not lank or treemaThangSelected ) and e ? . originalEvent ? . nativeEvent ? . which isnt 3
2014-09-28 17:00:48 -04:00
@ camera . zoomTo ( lank ? . sprite or @ camera . worldToSurface ( worldPos ) , @ camera . zoom , 1000 , true )
lank = null if @ options . choosing # Don't select lanks while choosing
if lank isnt @ selectedLank
@ selectedLank ? . selected = false
lank ? . selected = true
@selectedLank = lank
alive = lank and not ( lank . thang . health < 0 )
Backbone . Mediator . publish ' surface:sprite-selected ' ,
thang: if lank then lank . thang else null
sprite: lank
spellName: spellName ? e ? . spellName
originalEvent: e
worldPos: worldPos
@willSelectThang = null if lank # Now that we've done a real selection, don't reselect some other Thang later.
if alive and not @ suppressSelectionSounds
instance = lank . playSound ' selected '
if instance ? . playState is ' playSucceeded '
Backbone . Mediator . publish ' sprite:thang-began-talking ' , thang: lank ? . thang
instance . addEventListener ' complete ' , ->
Backbone . Mediator . publish ' sprite:thang-finished-talking ' , thang: lank ? . thang
onFlagColorSelected: (e) ->
@ removeLank @ flagCursorLank if @ flagCursorLank
@flagCursorLank = null
for flagLank in @ lankArray when flagLank . thangType . get ( ' name ' ) is ' Flag '
flagLank.sprite.cursor = if e . color then ' crosshair ' else ' pointer '
return unless e . color
@flagCursorLank = new FlagLank @ thangTypeFor ( ' Flag ' ) , @ createLankOptions ( thangID: ' Flag Cursor ' , color: e . color , team: me . team , isCursor: true , pos: e . pos )
@ addLank @ flagCursorLank , @ flagCursorLank . thang . id , @ layerAdapters [ ' Floating ' ]
onFlagUpdated: (e) ->
return unless e . active
pendingFlag = new FlagLank @ thangTypeFor ( ' Flag ' ) , @ createLankOptions ( thangID: ' Pending Flag ' + Math . random ( ) , color: e . color , team: e . team , isCursor: false , pos: e . pos )
@ addLank pendingFlag , pendingFlag . thang . id , @ layerAdapters [ ' Floating ' ]
@ pendingFlags . push pendingFlag
onFlagAppeared: (e) ->
# Remove the pending flag that matches this one's color/team/position, and any color/team matches placed earlier.
t1 = e . sprite . thang
pending = ( @ pendingFlags ? [ ] ) . slice ( )
foundExactMatch = false
for i in [ pending . length - 1 . . 0 ] by - 1
pendingFlag = pending [ i ]
t2 = pendingFlag . thang
matchedType = t1 . color is t2 . color and t1 . team is t2 . team
matched = matchedType and ( foundExactMatch or Math . abs ( t1 . pos . x - t2 . pos . x ) < 0.00001 and Math . abs ( t1 . pos . y - t2 . pos . y ) < 0.00001 )
if matched
foundExactMatch = true
@ pendingFlags . splice ( i , 1 )
@ removeLank pendingFlag
2014-11-06 21:18:57 -05:00
e . sprite . sprite ? . cursor = if @ flagCursorLank then ' crosshair ' else ' pointer '
2014-09-28 17:00:48 -04:00
null
onRemoveSelectedFlag: (e) ->
# Remove the selected lank if it's a flag, or any flag of the given color if a color is given.
flagLank = _ . find [ @ selectedLank ] . concat ( @ lankArray ) , (lank) ->
lank and lank . thangType . get ( ' name ' ) is ' Flag ' and lank . thang . team is me . team and ( lank . thang . color is e . color or not e . color ) and not lank . notOfThisWorld
return unless flagLank
Backbone . Mediator . publish ' surface:remove-flag ' , color: flagLank . thang . color
# Marks
updateSelection: ->
if @ selectedLank ? . thang and ( not @ selectedLank . thang . exists or not @ world . getThangByID @ selectedLank . thang . id )
thangID = @ selectedLank . thang . id
@selectedLank = null # Don't actually trigger deselection, but remove the selected lank.
@ selectionMark ? . toggle false
@willSelectThang = [ thangID , null ]
@ updateTarget ( )
return unless @ selectionMark
@selectedLank = null if @ selectedLank and ( @ selectedLank . destroyed or not @ selectedLank . thang )
# The selection mark should be on the ground layer, unless we're not a normal lank (like a wall), in which case we'll place it higher so we can see it.
if @ selectedLank and @ selectedLank . sprite . parent isnt @ layerAdapters . Default . container
@ selectionMark . setLayer @ layerAdapters . Default
else if @ selectedLank
@ selectionMark . setLayer @ layerAdapters . Ground
@ selectionMark . toggle @ selectedLank ?
@ selectionMark . setLank @ selectedLank
@ selectionMark . update ( )
updateTarget: ->
return unless @ targetMark
thang = @ selectedLank ? . thang
target = thang ? . target
targetPos = thang ? . targetPos
targetPos = null if targetPos ? . isZero ? ( ) # Null targetPos get serialized as (0, 0, 0)
@ targetMark . setLank if target then @ lanks [ target . id ] else null
@ targetMark . toggle @ targetMark . lank or targetPos
@ targetMark . update if targetPos then @ camera . worldToSurface targetPos else null