LevelBase->Level, more robust entity creation handler system
This commit is contained in:
parent
0f357ba02f
commit
326b4d8c78
|
|
@ -4,23 +4,24 @@ from ClockDelta import *
|
|||
from PythonUtil import Functor, sameElements, list2dict, uniqueElements
|
||||
import ToontownGlobals
|
||||
import DistributedObject
|
||||
import LevelBase
|
||||
import Level
|
||||
import DirectNotifyGlobal
|
||||
import EntityCreator
|
||||
|
||||
class DistributedLevel(DistributedObject.DistributedObject,
|
||||
LevelBase.LevelBase):
|
||||
Level.Level):
|
||||
"""DistributedLevel"""
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel')
|
||||
|
||||
WantVisibility = config.GetBool('level-visibility', 1)
|
||||
HideZones = config.GetBool('level-hidezones', 1)
|
||||
|
||||
# TODO: move level-model stuff to LevelMgr or FactoryLevelMgr?
|
||||
FloorCollPrefix = 'zoneFloor'
|
||||
|
||||
def __init__(self, cr):
|
||||
DistributedObject.DistributedObject.__init__(self, cr)
|
||||
LevelBase.LevelBase.__init__(self)
|
||||
Level.Level.__init__(self)
|
||||
|
||||
def generate(self):
|
||||
self.notify.debug('generate')
|
||||
|
|
@ -52,6 +53,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
|||
|
||||
def setScenarioIndex(self, scenarioIndex):
|
||||
self.scenarioIndex = scenarioIndex
|
||||
|
||||
# ugly hack: we treat these DC fields as if they were required,
|
||||
# and use 'levelAnnounceGenerate()' in place of regular old
|
||||
# announceGenerate(). Note that we have to call
|
||||
|
|
@ -65,8 +67,8 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
|||
def initializeLevel(self, spec):
|
||||
"""subclass should call this as soon as it's located its spec data.
|
||||
Must be called after obj has been generated."""
|
||||
LevelBase.LevelBase.initializeLevel(self, self.doId,
|
||||
spec, self.scenarioIndex)
|
||||
Level.Level.initializeLevel(self, self.doId,
|
||||
spec, self.scenarioIndex)
|
||||
|
||||
# all of the entities have been created now.
|
||||
# there should not be any pending reparents left at this point
|
||||
|
|
@ -83,23 +85,17 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
|||
Inheritors, override if desired."""
|
||||
return EntityCreator.EntityCreator(level=self)
|
||||
|
||||
def setupEntityCreationHandlers(self):
|
||||
LevelBase.LevelBase.setupEntityCreationHandlers(self)
|
||||
# load up the model ASAP so that we can get zone info out of it
|
||||
self.acceptOnce(self.getEntityTypeCreateEvent('levelMgr'),
|
||||
self.handleLevelMgrCreated)
|
||||
""" it would be nice to be able to do this, but this overrides the
|
||||
handler in LevelBase. For now, just override the LevelBase handler
|
||||
and call down.
|
||||
# fix up the model wrt zone collisions
|
||||
self.acceptOnce(self.getEntityTypeCreateEvent('zone'),
|
||||
self.handleAllZonesCreated)
|
||||
"""
|
||||
def onEntityTypePostCreate(self, entType):
|
||||
"""listen for certain entity types to be created"""
|
||||
Level.Level.onEntityTypePostCreate(self, entType)
|
||||
# NOTE: these handlers are private in order to avoid overriding
|
||||
# similar handlers in base classes
|
||||
if entType == 'levelMgr':
|
||||
self.__handleLevelMgrCreated()
|
||||
elif entType == 'zone':
|
||||
self.__handleAllZonesCreated()
|
||||
|
||||
def removeEntityCreationHandlers(self):
|
||||
LevelBase.LevelBase.removeEntityCreationHandlers(self)
|
||||
|
||||
def handleLevelMgrCreated(self):
|
||||
def __handleLevelMgrCreated(self):
|
||||
# as soon as the levelMgr has been created, load up the model
|
||||
# and extract zone info
|
||||
self.geom = self.levelMgr.geom
|
||||
|
|
@ -141,9 +137,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
|
|||
# find the doorway nodes
|
||||
self.doorwayNum2Node = findNumberedNodes('Doorway')
|
||||
|
||||
def handleAllZonesCreated(self):
|
||||
LevelBase.LevelBase.handleAllZonesCreated(self)
|
||||
|
||||
def __handleAllZonesCreated(self):
|
||||
# fix up the floor collisions for walkable zones before
|
||||
# any entities get put under the model
|
||||
for zoneNum in self.zoneNums:
|
||||
|
|
|
|||
|
|
@ -2,19 +2,19 @@
|
|||
|
||||
from ClockDelta import *
|
||||
import DistributedObjectAI
|
||||
import LevelBase
|
||||
import Level
|
||||
import DirectNotifyGlobal
|
||||
import EntityCreatorAI
|
||||
import WeightedChoice
|
||||
|
||||
class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
||||
LevelBase.LevelBase):
|
||||
Level.Level):
|
||||
"""DistributedLevelAI"""
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI')
|
||||
|
||||
def __init__(self, air, zoneId):
|
||||
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
|
||||
LevelBase.LevelBase.__init__(self)
|
||||
Level.Level.__init__(self)
|
||||
self.uberZoneId = zoneId
|
||||
|
||||
def generate(self, spec):
|
||||
|
|
@ -29,13 +29,12 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
|||
|
||||
def delete(self):
|
||||
self.notify.debug('delete')
|
||||
|
||||
self.destroyLevel()
|
||||
|
||||
# we do not allocate the uberZone for now, so don't deallocate it
|
||||
for zoneId in self.zoneIds[1:]:
|
||||
self.air.deallocateZone(zoneId)
|
||||
|
||||
self.destroyLevel()
|
||||
|
||||
DistributedObjectAI.DistributedObjectAI.delete(self)
|
||||
|
||||
def initializeLevel(self, spec):
|
||||
|
|
@ -52,27 +51,16 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
|
|||
# this will hold the network zoneIds that we allocate
|
||||
self.zoneIds = [self.uberZoneId]
|
||||
|
||||
LevelBase.LevelBase.initializeLevel(self, self.doId,
|
||||
spec, scenarioIndex)
|
||||
Level.Level.initializeLevel(self, self.doId,
|
||||
spec, scenarioIndex)
|
||||
|
||||
def createEntityCreator(self):
|
||||
"""Create the object that will be used to create Entities.
|
||||
Inheritors, override if desired."""
|
||||
return EntityCreatorAI.EntityCreatorAI(self.air, level=self)
|
||||
|
||||
def setupEntityCreationHandlers(self):
|
||||
LevelBase.LevelBase.setupEntityCreationHandlers(self)
|
||||
# listen for the creation of each zone object
|
||||
self.accept(self.getEntityOfTypeCreateEvent('zone'),
|
||||
self.zoneEntCreated)
|
||||
|
||||
def removeEntityCreationHandlers(self):
|
||||
LevelBase.LevelBase.removeEntityCreationHandlers(self)
|
||||
self.ignore(self.getEntityOfTypeCreateEvent('zone'))
|
||||
|
||||
def zoneEntCreated(self, entId):
|
||||
if entId == LevelBase.LevelBase.UberZoneEntId:
|
||||
return
|
||||
def allocateZoneId(self):
|
||||
# set aside a zoneId for each zone; this is called by ZoneEntityAI
|
||||
# there is error checking in air.allocateZone
|
||||
self.zoneIds.append(self.air.allocateZone())
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,256 @@
|
|||
"""Level.py: contains the Level class"""
|
||||
|
||||
import DirectNotifyGlobal
|
||||
import string
|
||||
from PythonUtil import lineInfo
|
||||
|
||||
"""
|
||||
Any data that can be edited by a level editor must be represented as
|
||||
an attribute of an entity owned by the level, in order to keep the
|
||||
level-editing interface simple and constant (there are at least three
|
||||
places where the entire editing interface must be duplicated).
|
||||
|
||||
To support this, we have entities such as 'levelMgr' and 'zoneEntity' that
|
||||
contain crucial level information, much of which is needed when setting
|
||||
up the level object, and is needed before other entity types can be
|
||||
effectively created. (If you try to create a distributed entity, but
|
||||
you don't yet have the information for the zone that it's in, because
|
||||
you haven't created the zone's ZoneEntity, you're hurting.)
|
||||
"""
|
||||
|
||||
"""
|
||||
ZONE TERMINOLOGY
|
||||
zoneNum / modelZoneNum : the number that a modeler chooses for a zone
|
||||
zoneEntId : the entity ID of the ZoneEntity that represents a zone
|
||||
zoneId : the network ID of the zone
|
||||
"""
|
||||
|
||||
class Level:
|
||||
"""Level: representation of a game level, keeps track of all of the
|
||||
entities and their interrelations, and creates and destroys entities"""
|
||||
notify = DirectNotifyGlobal.directNotify.newCategory('Level')
|
||||
|
||||
UberZoneEntId = 0
|
||||
|
||||
def __init__(self):
|
||||
self.spec = None
|
||||
|
||||
def initializeLevel(self, levelId, spec, scenarioIndex):
|
||||
""" subclass should call this as soon as it has located
|
||||
its spec data """
|
||||
self.levelId = levelId
|
||||
self.spec = spec
|
||||
self.scenarioIndex = scenarioIndex
|
||||
|
||||
# create a complete set of global and scenario-specific entity specs
|
||||
globalEntities = self.spec['globalEntities']
|
||||
scenarioEntities = self.spec['scenarios'][self.scenarioIndex][0]
|
||||
entId2spec = {}
|
||||
entId2spec.update(globalEntities)
|
||||
entId2spec.update(scenarioEntities)
|
||||
self.entId2spec = entId2spec
|
||||
|
||||
# create some handy tables
|
||||
|
||||
# entity type -> list of entIds
|
||||
self.entType2ids = {}
|
||||
for entId, spec in self.entId2spec.items():
|
||||
entType = spec['type']
|
||||
self.entType2ids.setdefault(entType, [])
|
||||
self.entType2ids[entType].append(entId)
|
||||
|
||||
# there should be one and only one levelMgr
|
||||
assert len(self.entType2ids['levelMgr']) == 1
|
||||
|
||||
# get an entity creator object
|
||||
self.entityCreator = self.createEntityCreator()
|
||||
# create all the entities
|
||||
self.createAllEntities(priorityTypes=['levelMgr','zone'])
|
||||
|
||||
def destroyLevel(self):
|
||||
if hasattr(self, 'createdEntities'):
|
||||
# destroy the entities in reverse order
|
||||
while len(self.createdEntities) > 0:
|
||||
entity = self.createdEntities.pop()
|
||||
self.notify.debug('destroying %s entity %s' %
|
||||
(self.getEntityType(entity.entId),
|
||||
entity.entId))
|
||||
entity.destroy()
|
||||
del self.createdEntities
|
||||
del self.entities
|
||||
del self.entId2spec
|
||||
del self.spec
|
||||
|
||||
def createEntityCreator(self):
|
||||
self.notify.error(
|
||||
'concrete Level class must override %s' % lineInfo()[2])
|
||||
|
||||
def createAllEntities(self, priorityTypes=[]):
|
||||
"""creates all entities in the spec. priorityTypes is an
|
||||
optional ordered list of entity types to create first."""
|
||||
# this will be filled in as the entities are created and report in
|
||||
# this includes distributed objects on the client
|
||||
self.entities = {}
|
||||
# this list contains the entities that we have actually created
|
||||
self.createdEntities = []
|
||||
|
||||
# get list of all entity types we need to create
|
||||
entTypes = self.entType2ids.keys()
|
||||
|
||||
self.onLevelPreCreate()
|
||||
|
||||
# first create the types in the priority list
|
||||
for type in priorityTypes:
|
||||
assert type in entTypes
|
||||
self.createAllEntitiesOfType(type)
|
||||
entTypes.remove(type)
|
||||
|
||||
# create the other entities in any old order
|
||||
for type in entTypes:
|
||||
self.createAllEntitiesOfType(type)
|
||||
|
||||
self.onLevelPostCreate()
|
||||
|
||||
def createAllEntitiesOfType(self, entType):
|
||||
"""creates all entities of a given type"""
|
||||
assert entType in self.entType2ids
|
||||
|
||||
self.onEntityTypePreCreate(entType)
|
||||
|
||||
for entId in self.entType2ids[entType]:
|
||||
self.createEntity(entId)
|
||||
|
||||
self.onEntityTypePostCreate(entType)
|
||||
|
||||
def createEntity(self, entId):
|
||||
assert not self.entities.has_key(entId)
|
||||
spec = self.entId2spec[entId]
|
||||
self.notify.debug('creating %s %s' % (spec['type'], entId))
|
||||
entity = self.entityCreator.createEntity(entId)
|
||||
# NOTE: the entity is not considered to really be created until
|
||||
# it has all of its initial spec data; see 'initializeEntity'
|
||||
# below.
|
||||
if entity is not None:
|
||||
self.createdEntities.append(entity)
|
||||
return entity
|
||||
|
||||
def initializeEntity(self, entity):
|
||||
"""populate an entity with its spec data. This is not done
|
||||
in createEntity in order to allow other pieces of code to create
|
||||
entities; this is called directly by Entity.
|
||||
"""
|
||||
entId = entity.entId
|
||||
spec = self.entId2spec[entId]
|
||||
# on initialization, set items directly on entity
|
||||
for key,value in spec.items():
|
||||
if key in ('type', 'name', 'comment',):
|
||||
continue
|
||||
entity.setAttribInit(key, value)
|
||||
|
||||
# entity is initialized, add it to the list of entities
|
||||
self.entities[entity.entId] = entity
|
||||
|
||||
# call the create handler
|
||||
self.onEntityCreate(entId)
|
||||
|
||||
def getEntity(self, entId):
|
||||
return self.entities[entId]
|
||||
|
||||
def getEntityType(self, entId):
|
||||
return self.entId2spec[entId]['type']
|
||||
|
||||
def getZoneId(self, dummy=None, zoneNum=None, entId=None):
|
||||
"""look up network zoneId by zoneNum or entId"""
|
||||
assert (zoneNum is not None) or (entId is not None)
|
||||
assert not ((zoneNum is not None) and (entId is not None))
|
||||
if zoneNum is not None:
|
||||
assert zoneNum in self.zoneNum2zoneId
|
||||
return self.zoneNum2zoneId[zoneNum]
|
||||
else:
|
||||
assert entId in self.zoneEntId2zoneId
|
||||
return self.zoneEntId2zoneId[entId]
|
||||
|
||||
# these events are thrown as the level initializes itself
|
||||
# LEVEL
|
||||
def getLevelPreCreateEvent(self):
|
||||
"""This is the event that is thrown immediately before the level
|
||||
creates its entities."""
|
||||
return 'levelPreCreate-%s' % (self.levelId)
|
||||
def getLevelPostCreateEvent(self):
|
||||
"""This is the event that is thrown immediately after the level
|
||||
creates its entities."""
|
||||
return 'levelPostCreate-%s' % (self.levelId)
|
||||
# ENTITY TYPE
|
||||
def getEntityTypePreCreateEvent(self, entType):
|
||||
"""This is the event that is thrown immediately before the level
|
||||
creates the entities of the given type."""
|
||||
return 'entityTypePreCreate-%s-%s' % (self.levelId, entType)
|
||||
def getEntityTypePostCreateEvent(self, entType):
|
||||
"""This is the event that is thrown immediately after the level
|
||||
creates the entities of the given type."""
|
||||
return 'entityTypePostCreate-%s-%s' % (self.levelId, entType)
|
||||
# ENTITY
|
||||
def getEntityCreateEvent(self, entId):
|
||||
"""This is the event that is thrown immediately after a
|
||||
particular entity is initialized"""
|
||||
return 'entityCreate-%s-%s' % (self.levelId, entId)
|
||||
def getEntityOfTypeCreateEvent(self, entType):
|
||||
"""This event is thrown immediately after each instance of the
|
||||
given entity type is created; handlers must accept an entId"""
|
||||
return 'entityOfTypeCreate-%s-%s' % (self.levelId, entType)
|
||||
|
||||
# these handlers are called as the level initializes itself
|
||||
# LEVEL
|
||||
def onLevelPreCreate(self):
|
||||
"""Level is about to create its entities"""
|
||||
messenger.send(self.getLevelPreCreateEvent())
|
||||
def onLevelPostCreate(self):
|
||||
"""Level is done creating its entities"""
|
||||
messenger.send(self.getLevelPostCreateEvent())
|
||||
# ENTITY TYPE
|
||||
def onEntityTypePreCreate(self, entType):
|
||||
"""Level is about to create these entities"""
|
||||
messenger.send(self.getEntityTypePreCreateEvent(entType))
|
||||
def onEntityTypePostCreate(self, entType):
|
||||
"""Level has just created these entities"""
|
||||
if entType == 'zone':
|
||||
self.__handleAllZonesCreated()
|
||||
messenger.send(self.getEntityTypePostCreateEvent(entType))
|
||||
# ENTITY
|
||||
def onEntityCreate(self, entId):
|
||||
"""Level has just created this entity"""
|
||||
# send the entity-create event
|
||||
messenger.send(self.getEntityCreateEvent(entId))
|
||||
# send the entity-of-type create event
|
||||
messenger.send(
|
||||
self.getEntityOfTypeCreateEvent(self.getEntityType(entId)),
|
||||
[entId])
|
||||
|
||||
def __handleAllZonesCreated(self):
|
||||
"""once all the zone entities have been created, and we've got a
|
||||
list of zoneIds in self.zoneIds, init zone tables"""
|
||||
# create a table mapping the model's zone numbers to the zone
|
||||
# entIds; zone entities are tied to model zone nums in the level spec,
|
||||
# this is just for convenient lookup
|
||||
self.zoneNum2entId = {}
|
||||
for entId in self.entType2ids['zone']:
|
||||
zoneEnt = self.getEntity(entId)
|
||||
# NOTE: modelZoneNum comes from the entity's spec data
|
||||
self.zoneNum2entId[zoneEnt.modelZoneNum] = entId
|
||||
|
||||
# At this point, we need to have a 'self.zoneIds' table of network
|
||||
# zoneIds, one for each zone including the UberZone. This is where
|
||||
# we decide which zoneNum/entId gets mapped to which zoneId.
|
||||
# We sort the zoneNums, and then pair the sorted zoneNums up with the
|
||||
# zoneIds in the order that they appear in the self.zoneIds table.
|
||||
modelZoneNums = self.zoneNum2entId.keys()
|
||||
modelZoneNums.sort()
|
||||
# maps of zoneNum/zoneEntId to network zoneId
|
||||
self.zoneNum2zoneId = {}
|
||||
self.zoneEntId2zoneId = {}
|
||||
for i in range(len(modelZoneNums)):
|
||||
modelZoneNum = modelZoneNums[i]
|
||||
zoneEntId = self.zoneNum2entId[modelZoneNum]
|
||||
zoneId = self.zoneIds[i]
|
||||
self.zoneNum2zoneId[modelZoneNum] = zoneId
|
||||
self.zoneEntId2zoneId[zoneEntId] = zoneId
|
||||
|
|
@ -5,3 +5,5 @@ import Entity
|
|||
class ZoneEntityAI(Entity.Entity):
|
||||
def __init__(self, level, entId):
|
||||
Entity.Entity.__init__(self, level, entId)
|
||||
# allocate a network zoneId for each zone
|
||||
self.level.allocateZoneId()
|
||||
|
|
|
|||
Loading…
Reference in New Issue