2014-01-03 13:32:13 -05:00
ThangState = require ' ./thang_state '
{ thangNames } = require ' ./names '
{ ArgumentError } = require ' ./errors '
2014-01-11 15:41:13 -05:00
Rand = require ' ./rand '
2014-01-03 13:32:13 -05:00
module.exports = class Thang
2014-01-29 13:14:12 -05:00
@className: ' Thang '
@remainingThangNames: { }
2014-01-11 16:09:25 -05:00
2014-01-29 13:14:12 -05:00
@nextID: (spriteName, world) ->
originals = thangNames [ spriteName ] or [ spriteName ]
remaining = Thang . remainingThangNames [ spriteName ]
remaining = Thang . remainingThangNames [ spriteName ] = originals . slice ( ) unless remaining ? . length
2014-01-14 17:03:55 -05:00
2014-01-29 13:14:12 -05:00
baseName = remaining . splice ( world . rand . rand ( remaining . length ) , 1 ) [ 0 ]
i = 0
while true
name = if i then " #{ baseName } #{ i } " else baseName
extantThang = world . thangMap [ name ]
break unless extantThang
i ++
name
2014-01-31 19:16:59 -05:00
2014-01-29 13:14:12 -05:00
@resetThangIDs: -> Thang.remainingThangNames = { }
2014-03-13 20:59:17 -04:00
isThang: true
2014-01-31 19:16:59 -05:00
apiProperties: [ ' id ' , ' spriteName ' , ' health ' , ' pos ' , ' team ' ]
2014-01-03 13:32:13 -05:00
constructor: (@world, @spriteName, @id) ->
@ spriteName ? = @ constructor . className
2014-01-29 13:14:12 -05:00
@ id ? = @ constructor . nextID @ spriteName , @ world
2014-01-03 13:32:13 -05:00
@ addTrackedProperties [ ' exists ' , ' boolean ' ] # TODO: move into Systems/Components, too?
#console.log "Generated #{@toString()}."
2014-05-22 22:05:05 -04:00
destroy: ->
# Just trying to destroy __aetherAPIClone, but might as well nuke everything just in case
@ [ key ] = undefined for key of @
@destroyed = true
@destroy = ->
2014-01-03 13:32:13 -05:00
updateRegistration: ->
system . register @ for system in @ world . systems
publishNote: (channel, event) ->
event.thang = @
@ world . publishNote channel , event
2014-04-03 19:16:53 -04:00
2014-05-13 13:42:48 -04:00
getGoalState: (goalID) ->
@ world . getGoalState goalID
2014-05-22 22:05:05 -04:00
2014-03-28 21:53:19 -04:00
setGoalState: (goalID, status) ->
@ world . setGoalState goalID , status
2014-01-03 13:32:13 -05:00
2014-04-11 17:03:13 -04:00
getThangByID: (id) ->
@ world . getThangByID id
2014-01-03 13:32:13 -05:00
addComponents: (components...) ->
# We don't need to keep the components around after attaching them, but we will keep their initial config for recreating Thangs
@ components ? = [ ]
for [ componentClass , componentConfig ] in components
@ components . push [ componentClass , componentConfig ]
if _ . isString componentClass # We had already turned it into a string, so re-classify it momentarily
componentClass = @ world . classMap [ componentClass ]
else
@ world ? . classMap [ componentClass . className ] ? = componentClass
2014-08-11 20:09:36 -04:00
c = new componentClass componentConfig ? { }
2014-01-03 13:32:13 -05:00
c . attach @
# [prop, type]s of properties which have values tracked across WorldFrames. Also call keepTrackedProperty some non-expensive time when you change it or it will be skipped.
addTrackedProperties: (props...) ->
@ trackedPropertiesKeys ? = [ ]
@ trackedPropertiesTypes ? = [ ]
@ trackedPropertiesUsed ? = [ ]
for [ prop , type ] in props
unless type in ThangState . trackedPropertyTypes
# How should errors for busted Components work? We can't recover from this and run the world.
throw new Error " Type #{ type } for property #{ prop } is not a trackable property type: #{ trackedPropertyTypes } "
oldPropIndex = @ trackedPropertiesKeys . indexOf prop
if oldPropIndex is - 1
@ trackedPropertiesKeys . push prop
@ trackedPropertiesTypes . push type
@ trackedPropertiesUsed . push false
else
oldType = @ trackedPropertiesTypes [ oldPropIndex ]
if type isnt oldType
throw new Error " Two types were specified for trackable property #{ prop } : #{ oldType } and #{ type } . "
keepTrackedProperty: (prop) ->
2014-08-22 15:39:29 -04:00
# Wish we could do this faster, but I can't think of how.
2014-01-03 13:32:13 -05:00
propIndex = @ trackedPropertiesKeys . indexOf prop
if propIndex isnt - 1
@ trackedPropertiesUsed [ propIndex ] = true
# @trackedFinalProperties: names of properties which need to be tracked once at the end of the World; don't worry about types
addTrackedFinalProperties: (props...) ->
@ trackedFinalProperties ? = [ ]
@trackedFinalProperties = @ trackedFinalProperties . concat ( k for k in props when not ( k in @ trackedFinalProperties ) )
getState: ->
@_state = new ThangState @
setState: (state) ->
@_state = state . restore ( )
toString: -> @ id
createMethodChain: (methodName) ->
@ methodChains ? = { }
chain = @ methodChains [ methodName ]
return chain if chain
chain = @ methodChains [ methodName ] = { original: @ [ methodName ] , user: null , components: [ ] }
@ [ methodName ] = _ . partial @ callChainedMethod , methodName # Optimize! _.partial is fastest I've found
chain
appendMethod: (methodName, newMethod) ->
# Components add methods that come after the original method
@ createMethodChain ( methodName ) . components . push newMethod
callChainedMethod: (methodName, args...) ->
# Optimize this like crazy--but how?
chain = @ methodChains [ methodName ]
primaryMethod = chain . user or chain . original
ret = primaryMethod ? . apply @ , args
for componentMethod in chain . components
ret2 = componentMethod . apply @ , args
ret = ret2 ? ret # override return value only if not null
ret
getMethodSource: (methodName) ->
source = { }
if @ methodChains ? and methodName of @ methodChains
chain = @ methodChains [ methodName ]
source.original = chain . original . toString ( )
source.user = chain . user ? . toString ( )
else
2014-06-30 22:16:26 -04:00
source.original = @ [ methodName ] ? . toString ( ) ? ' '
2014-01-03 13:32:13 -05:00
source.original = Aether . getFunctionBody source . original
source
serialize: ->
o = { spriteName: @ spriteName , id: @ id , components: [ ] , finalState: { } }
for [ componentClass , componentConfig ] , i in ( @ components ? [ ] )
if _ . isString componentClass
componentClassName = componentClass
else
componentClassName = componentClass . className
@ world . classMap [ componentClass . className ] ? = componentClass
o . components . push [ componentClassName , componentConfig ]
for trackedFinalProperty in @ trackedFinalProperties ? [ ]
# TODO: take some (but not all) of serialize logic from ThangState to handle other types
o . finalState [ trackedFinalProperty ] = @ [ trackedFinalProperty ]
2014-08-22 15:39:29 -04:00
# Since we might keep tracked properties later during streaming, we need to know which we think are unused.
o.unusedTrackedPropertyKeys = ( @ trackedPropertiesKeys [ propIndex ] for used , propIndex in @ trackedPropertiesUsed when not used )
2014-01-03 13:32:13 -05:00
o
2014-10-23 21:11:12 -04:00
@deserialize: (o, world, classMap, levelComponents) ->
2014-01-03 13:32:13 -05:00
t = new Thang world , o . spriteName , o . id
for [ componentClassName , componentConfig ] in o . components
2014-10-23 21:11:12 -04:00
unless componentClass = classMap [ componentClassName ]
console . debug ' Compiling new Component while deserializing: ' , componentClassName
componentModel = _ . find levelComponents , name: componentClassName
componentClass = world . loadClassFromCode componentModel . js , componentClassName , ' component '
world . classMap [ componentClassName ] = componentClass
2014-01-03 13:32:13 -05:00
t . addComponents [ componentClass , componentConfig ]
2014-08-22 15:39:29 -04:00
t.unusedTrackedPropertyKeys = o . unusedTrackedPropertyKeys
t.unusedTrackedPropertyValues = ( t [ prop ] for prop in o . unusedTrackedPropertyKeys )
2014-01-03 13:32:13 -05:00
for prop , val of o . finalState
# TODO: take some (but not all) of deserialize logic from ThangState to handle other types
t [ prop ] = val
2014-01-14 16:16:30 -05:00
t
2014-01-14 17:03:55 -05:00
2014-01-24 16:03:04 -05:00
serializeForAether: ->
{ CN: @ constructor . className , id: @ id }
2014-09-28 17:00:48 -04:00
getLankOptions: ->
2014-08-26 15:39:30 -04:00
colorConfigs = @ teamColors or @ world ? . getTeamColors ( ) or { }
2014-08-24 15:33:46 -04:00
options = { colorConfig: { } }
2014-11-23 23:04:07 -05:00
if @ id is ' Hero Placeholder ' and not @ world . getThangByID ' Hero Placeholder 1 '
return options # No team colors for heroes on single-player levels
2014-08-24 15:33:46 -04:00
if @ team and teamColor = colorConfigs [ @ team ]
options.colorConfig.team = teamColor
if @ color and color = @ grabColorConfig @ color
options.colorConfig.color = color
2014-12-16 15:59:39 -05:00
if @ colors
options . colorConfig [ colorType ] = colorValue for colorType , colorValue of @ colors
2014-01-14 16:16:30 -05:00
options
2014-08-24 15:33:46 -04:00
grabColorConfig: (color) ->
{
green: { hue: 0.33 , saturation: 0.5 , lightness: 0.5 }
black: { hue: 0 , saturation: 0 , lightness: 0.25 }
violet: { hue: 0.83 , saturation: 0.5 , lightness: 0.5 }
} [ color ]