From 326b4d8c78dd4bc7a679e9bc2fe7e36bf16a71df Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Mon, 22 Sep 2003 22:24:08 +0000 Subject: [PATCH] LevelBase->Level, more robust entity creation handler system --- direct/src/level/DistributedLevel.py | 42 ++-- direct/src/level/DistributedLevelAI.py | 30 +-- direct/src/level/Level.py | 256 +++++++++++++++++++++++++ direct/src/level/ZoneEntityAI.py | 2 + 4 files changed, 285 insertions(+), 45 deletions(-) create mode 100755 direct/src/level/Level.py diff --git a/direct/src/level/DistributedLevel.py b/direct/src/level/DistributedLevel.py index 8618468ea1..8b2fb83a46 100755 --- a/direct/src/level/DistributedLevel.py +++ b/direct/src/level/DistributedLevel.py @@ -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: diff --git a/direct/src/level/DistributedLevelAI.py b/direct/src/level/DistributedLevelAI.py index 4215f5238f..587635c6b4 100755 --- a/direct/src/level/DistributedLevelAI.py +++ b/direct/src/level/DistributedLevelAI.py @@ -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()) diff --git a/direct/src/level/Level.py b/direct/src/level/Level.py new file mode 100755 index 0000000000..3a5b822691 --- /dev/null +++ b/direct/src/level/Level.py @@ -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 diff --git a/direct/src/level/ZoneEntityAI.py b/direct/src/level/ZoneEntityAI.py index 6b20882757..c76f598456 100755 --- a/direct/src/level/ZoneEntityAI.py +++ b/direct/src/level/ZoneEntityAI.py @@ -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()