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) ->
2014-01-03 13:32:13 -05:00
Thang . lastIDNums ? = { }
2014-01-29 13:14:12 -05:00
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-01-03 13:32:13 -05:00
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()}."
updateRegistration: ->
system . register @ for system in @ world . systems
publishNote: (channel, event) ->
event.thang = @
@ world . publishNote channel , event
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
c = new componentClass componentConfig
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) ->
# Hmm; can we do this faster?
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
source.original = @ [ methodName ] ? . toString ( ) ? " "
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 ]
o
@deserialize: (o, world, classMap) ->
t = new Thang world , o . spriteName , o . id
for [ componentClassName , componentConfig ] in o . components
componentClass = classMap [ componentClassName ]
t . addComponents [ componentClass , componentConfig ]
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-01-14 16:16:30 -05:00
getSpriteOptions: ->
colorConfigs = @ world ? . getTeamColors ( ) or { }
options = { }
if @ team and colorConfigs [ @ team ]
options.colorConfig = { team: colorConfigs [ @ team ] }
options