mirror of
https://github.com/codeninjasllc/codecombat.git
synced 2024-12-04 21:01:06 -05:00
191 lines
7.7 KiB
CoffeeScript
191 lines
7.7 KiB
CoffeeScript
ThangState = require './thang_state'
|
|
{thangNames} = require './names'
|
|
{ArgumentError} = require './errors'
|
|
Rand = require './rand'
|
|
|
|
module.exports = class Thang
|
|
@className: 'Thang'
|
|
@remainingThangNames: {}
|
|
|
|
@nextID: (spriteName, world) ->
|
|
originals = thangNames[spriteName] or [spriteName]
|
|
remaining = Thang.remainingThangNames[spriteName]
|
|
remaining = Thang.remainingThangNames[spriteName] = originals.slice() unless remaining?.length
|
|
|
|
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
|
|
|
|
@resetThangIDs: -> Thang.remainingThangNames = {}
|
|
isThang: true
|
|
apiProperties: ['id', 'spriteName', 'health', 'pos', 'team']
|
|
|
|
constructor: (@world, @spriteName, @id) ->
|
|
@spriteName ?= @constructor.className
|
|
@id ?= @constructor.nextID @spriteName, @world
|
|
@addTrackedProperties ['exists', 'boolean'] # TODO: move into Systems/Components, too?
|
|
#console.log "Generated #{@toString()}."
|
|
|
|
destroy: ->
|
|
# Just trying to destroy __aetherAPIClone, but might as well nuke everything just in case
|
|
@[key] = undefined for key of @
|
|
@destroyed = true
|
|
@destroy = ->
|
|
|
|
updateRegistration: ->
|
|
system.register @ for system in @world.systems
|
|
|
|
publishNote: (channel, event) ->
|
|
event.thang = @
|
|
@world.publishNote channel, event
|
|
|
|
getGoalState: (goalID) ->
|
|
@world.getGoalState goalID
|
|
|
|
setGoalState: (goalID, status) ->
|
|
@world.setGoalState goalID, status
|
|
|
|
getThangByID: (id) ->
|
|
@world.getThangByID id
|
|
|
|
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: #{ThangState.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) ->
|
|
# Wish we could do this faster, but I can't think of how.
|
|
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]
|
|
# 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)
|
|
o
|
|
|
|
@deserialize: (o, world, classMap, levelComponents) ->
|
|
t = new Thang world, o.spriteName, o.id
|
|
for [componentClassName, componentConfig] in o.components
|
|
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
|
|
t.addComponents [componentClass, componentConfig]
|
|
t.unusedTrackedPropertyKeys = o.unusedTrackedPropertyKeys
|
|
t.unusedTrackedPropertyValues = (t[prop] for prop in o.unusedTrackedPropertyKeys)
|
|
for prop, val of o.finalState
|
|
# TODO: take some (but not all) of deserialize logic from ThangState to handle other types
|
|
t[prop] = val
|
|
t
|
|
|
|
serializeForAether: ->
|
|
{CN: @constructor.className, id: @id}
|
|
|
|
getLankOptions: ->
|
|
colorConfigs = @teamColors or @world?.getTeamColors() or {}
|
|
options = {colorConfig: {}}
|
|
if @id is 'Hero Placeholder' and not @world.getThangByID 'Hero Placeholder 1'
|
|
return options # No team colors for heroes on single-player levels
|
|
if @team and teamColor = colorConfigs[@team]
|
|
options.colorConfig.team = teamColor
|
|
if @color and color = @grabColorConfig @color
|
|
options.colorConfig.color = color
|
|
if @colors
|
|
options.colorConfig[colorType] = colorValue for colorType, colorValue of @colors
|
|
options
|
|
|
|
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]
|