From 4b4c1e3a624bcef667c79c1e6d40011fd5cf4667 Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Wed, 24 Sep 2003 01:29:08 +0000 Subject: [PATCH] code reorg; handling of dynamic zone entity creation and deletion --- direct/src/level/DistributedLevel.py | 33 +++++------ direct/src/level/DistributedLevelAI.py | 29 +++------- direct/src/level/Entity.py | 24 ++++---- direct/src/level/Level.py | 55 +++++++------------ direct/src/level/LevelMgr.py | 76 ++++++++++++++++++++++++-- direct/src/level/LevelMgrAI.py | 68 +++++++++++++++++++++-- direct/src/level/LevelMgrBase.py | 14 +++++ direct/src/level/ZoneEntity.py | 6 +- direct/src/level/ZoneEntityAI.py | 22 ++++++-- direct/src/level/ZoneEntityBase.py | 23 ++++++++ 10 files changed, 246 insertions(+), 104 deletions(-) create mode 100755 direct/src/level/LevelMgrBase.py create mode 100755 direct/src/level/ZoneEntityBase.py diff --git a/direct/src/level/DistributedLevel.py b/direct/src/level/DistributedLevel.py index 8b2fb83a46..5b2b30991e 100755 --- a/direct/src/level/DistributedLevel.py +++ b/direct/src/level/DistributedLevel.py @@ -40,7 +40,7 @@ class DistributedLevel(DistributedObject.DistributedObject, # all our entities at that time. toonbase.tcr.timeManager.synchronize('DistributedLevel.generate') - # required fields (these ought to be required fields, but + # "required" fields (these ought to be required fields, but # the AI level obj doesn't know the data values until it has been # generated.) def setZoneIds(self, zoneIds): @@ -92,8 +92,6 @@ class DistributedLevel(DistributedObject.DistributedObject, # similar handlers in base classes if entType == 'levelMgr': self.__handleLevelMgrCreated() - elif entType == 'zone': - self.__handleAllZonesCreated() def __handleLevelMgrCreated(self): # as soon as the levelMgr has been created, load up the model @@ -129,20 +127,9 @@ class DistributedLevel(DistributedObject.DistributedObject, self.zoneNums.sort() self.notify.debug('zones: %s' % self.zoneNums) - # hack in another doorway - dw = self.geom.attachNewNode('Doorway27') - dw.setPos(-49.4,86.7,19.26) - dw.setH(0) - - # find the doorway nodes - self.doorwayNum2Node = findNumberedNodes('Doorway') - - def __handleAllZonesCreated(self): - # fix up the floor collisions for walkable zones before + # fix up the floor collisions for walkable zones *before* # any entities get put under the model - for zoneNum in self.zoneNums: - zoneNode = self.zoneNum2node[zoneNum] - + for zoneNum,zoneNode in self.zoneNum2node.items(): # if this is a walkable zone, fix up the model allColls = zoneNode.findAllMatches('**/+CollisionNode').asList() # which of them, if any, are floors? @@ -170,6 +157,14 @@ class DistributedLevel(DistributedObject.DistributedObject, self.toonEnterZone(zoneNum) self.accept('enter%s' % floorCollName, handleZoneEnter) + # hack in another doorway + dw = self.geom.attachNewNode('Doorway27') + dw.setPos(-49.4,86.7,19.26) + dw.setH(0) + + # find the doorway nodes + self.doorwayNum2Node = findNumberedNodes('Doorway') + def announceGenerate(self): self.notify.debug('announceGenerate') DistributedObject.DistributedObject.announceGenerate(self) @@ -270,7 +265,9 @@ class DistributedLevel(DistributedObject.DistributedObject, # if no viz, listen to all the zones if not DistributedLevel.WantVisibility: - self.setVisibility(self.zoneNums) + zoneNums = list(self.zoneNums) + zoneNums.remove(Level.Level.uberZoneNum) + self.setVisibility(zoneNums) def toonEnterZone(self, zoneNum): self.notify.debug('toonEnterZone%s' % zoneNum) @@ -328,7 +325,7 @@ class DistributedLevel(DistributedObject.DistributedObject, # accepts list of visible zone numbers # convert the zone numbers into their actual zoneIds # always include Toontown and factory uberZones - factoryUberZone = self.getZoneId(zoneNum=0) + factoryUberZone = self.getZoneId(zoneNum=Level.Level.UberZoneNum) visibleZoneIds = [ToontownGlobals.UberZone, factoryUberZone] for vz in vizList: visibleZoneIds.append(self.getZoneId(zoneNum=vz)) diff --git a/direct/src/level/DistributedLevelAI.py b/direct/src/level/DistributedLevelAI.py index 7b7b2282f5..23b1c4e73c 100755 --- a/direct/src/level/DistributedLevelAI.py +++ b/direct/src/level/DistributedLevelAI.py @@ -30,11 +30,6 @@ 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) - DistributedObjectAI.DistributedObjectAI.delete(self) def initializeLevel(self, spec): @@ -48,9 +43,6 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, scenario = wc.choose() scenarioIndex = spec['scenarios'].index(scenario) - # this will hold the network zoneIds that we allocate - self.zoneIds = [self.uberZoneId] - Level.Level.initializeLevel(self, self.doId, spec, scenarioIndex) @@ -59,30 +51,23 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI, Inheritors, override if desired.""" return EntityCreatorAI.EntityCreatorAI(self.air, level=self) - 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()) - def getEntityZoneId(self, entId): """figure out what network zoneId an entity is in""" - # TODO: where should the zone info come from? It could come - # from the 'parent' scene-graph info... but what about intangible - # distributed objects? I guess if they don't inherit from - # NodePathEntity et al, they'll just have an unused 'parent' - # attribute hanging around, which isn't the end of the world... - # this func is called before the entity has been created; look - # into the spec data, since we can't get a handle on the object itself + # into the spec data, since we can't yet get a handle on the + # object itself at this point spec = self.entId2spec[entId] type = spec['type'] if type == 'zone': if not hasattr(self, 'zoneNum2zoneId'): - # we haven't even created our zone entities yet; + # we haven't even started creating our zone entities yet; # we have no idea yet which zoneNums map to which # network zoneIds. just return None. return None - return self.zoneNum2zoneId[spec['modelZoneNum']] + # If the entity *is* the zone, it will not yet be in the + # table; but since zone entities are currently not distributed, + # it's fine to return None. + return self.zoneNum2zoneId.get(spec['modelZoneNum']) if not spec.has_key('parent'): return None return self.getEntityZoneId(spec['parent']) diff --git a/direct/src/level/Entity.py b/direct/src/level/Entity.py index 5525b66315..2a4d8492af 100755 --- a/direct/src/level/Entity.py +++ b/direct/src/level/Entity.py @@ -1,10 +1,11 @@ """Entity.py: contains the Entity class""" +from DirectObject import * +from PythonUtil import lineInfo import string import DirectNotifyGlobal -from PythonUtil import lineInfo -class Entity: +class Entity(DirectObject): """Entity is the base class for all objects that exist in a Level and can be edited with the LevelEditor.""" notify = DirectNotifyGlobal.directNotify.newCategory('Entity') @@ -32,6 +33,7 @@ class Entity: return 'ent%s(%s)' % (self.entId, self.level.getEntityType(self.entId)) def destroy(self): + self.level.onEntityDestroy(self.entId) del self.level def privGetSetter(self, attrib): @@ -40,6 +42,15 @@ class Entity: return getattr(self, setFuncName) return None + def callSetters(self, *attribs): + """call this with a list of attribs, and any that exist on the + entity and have setters will be passed to their setter""" + self.privCallSetters(0, *attribs) + + def callSettersAndDelete(self, *attribs): + """same as callSetters, but also removes attribs from entity""" + self.privCallSetters(1, *attribs) + def privCallSetters(self, doDelete, *attribs): """common implementation of callSetters and callSettersAndDelete""" for attrib in attribs: @@ -51,15 +62,6 @@ class Entity: delattr(self, attrib) setter(value) - def callSetters(self, *attribs): - """call this with a list of attribs, and any that exist on the - entity and have setters will be passed to their setter""" - self.privCallSetters(0, *attribs) - - def callSettersAndDelete(self, *attribs): - """same as callSetters, but also removes attribs from entity""" - self.privCallSetters(1, *attribs) - # this will be called with each item of our spec data on initialization def setAttribInit(self, attrib, value): if hasattr(self, attrib): diff --git a/direct/src/level/Level.py b/direct/src/level/Level.py index 3a5b822691..15075c7e3a 100755 --- a/direct/src/level/Level.py +++ b/direct/src/level/Level.py @@ -30,6 +30,7 @@ class Level: entities and their interrelations, and creates and destroys entities""" notify = DirectNotifyGlobal.directNotify.newCategory('Level') + UberZoneNum = 0 UberZoneEntId = 0 def __init__(self): @@ -61,10 +62,13 @@ class Level: # there should be one and only one levelMgr assert len(self.entType2ids['levelMgr']) == 1 + # make sure the uberzone is there + assert Level.UberZoneEntId in self.entType2ids['zone'] # get an entity creator object self.entityCreator = self.createEntityCreator() # create all the entities + # TODO: we should leave this to a subclass or the level user self.createAllEntities(priorityTypes=['levelMgr','zone']) def destroyLevel(self): @@ -132,6 +136,13 @@ class Level: # below. if entity is not None: self.createdEntities.append(entity) + + # call the create handler + # we used to do this in initializeEntity, but that did not + # allow for additional initialization to be performed in + # derived entity __init__ funcs + self.onEntityCreate(entId) + return entity def initializeEntity(self, entity): @@ -150,11 +161,8 @@ class Level: # 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] + return self.entities.get(entId) def getEntityType(self, entId): return self.entId2spec[entId]['type'] @@ -213,8 +221,6 @@ class Level: 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): @@ -226,31 +232,12 @@ class Level: 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 + # these are events and handlers that are invoked as entities are destroyed + def getEntityDestroyEvent(self, entId): + """This is the event that is thrown immediately before an + entity is destroyed""" + return 'entityDestroy-%s-%s' % (self.levelId, entId) + def onEntityDestroy(self, entId): + """Level is about to destroy this entity""" + # send the entity-destroy event + messenger.send(self.getEntityDestroyEvent(entId)) diff --git a/direct/src/level/LevelMgr.py b/direct/src/level/LevelMgr.py index bae70d1b95..fff39eeb1f 100755 --- a/direct/src/level/LevelMgr.py +++ b/direct/src/level/LevelMgr.py @@ -1,18 +1,82 @@ """LevelMgr module: contains the LevelMgr class""" -import Entity +from PythonUtil import Functor +import LevelMgrBase -class LevelMgr(Entity.Entity): +class LevelMgr(LevelMgrBase.LevelMgrBase): """This class manages editable client-side level attributes""" def __init__(self, level, entId): - Entity.Entity.__init__(self, level, entId) - self.level.levelMgr = self + LevelMgrBase.LevelMgrBase.__init__(self, level, entId) # load the model self.geom = loader.loadModel(self.modelFilename) + # modelZoneNum -> zone entId + self.level.zoneNum2entId = {} + # modelZoneNum -> network zoneId + self.level.zoneNum2zoneId = {} + # zone entId -> network zoneId + self.level.zoneEntId2zoneId = {} + + # listen for every zone creation + self.accept(self.level.getEntityOfTypeCreateEvent('zone'), + self.handleZoneCreated) + def destroy(self): - del self.level.levelMgr + del self.level.zoneIds + del self.level.zoneEntId2zoneId + del self.level.zoneNum2zoneId + del self.level.zoneNum2entId self.geom.removeNode() del self.geom - Entity.Entity.destroy(self) + LevelMgrBase.LevelMgrBase.destroy(self) + + def handleZoneCreated(self, entId): + zoneEnt = self.level.getEntity(entId) + + # register the zone's info in the tables + # right off the bat, we have the zone's modelZoneNum and entId + self.level.zoneNum2entId[zoneEnt.modelZoneNum] = entId + + # we can assume that we have a complete list of zoneIds in + # self.level.zoneIds. As each zone entity is created, set up + # as if we have all of the zone entities. This allows dynamic + # zone entity creation and deletion during editing. + # TODO: we should delay this until all zone entities have been + # created on level init + self.privAssignZoneIds() + + # listen for the zone's destruction + self.accept(self.level.getEntityDestroyEvent(entId), + Functor(self.handleZoneDestroy, entId)) + + def handleZoneDestroy(self, entId): + zoneEnt = self.level.getEntity(entId) + # unregister the zone from the maps + del self.level.zoneNum2entId[zoneEnt.modelZoneNum] + del self.level.zoneNum2zoneId[zoneEnt.modelZoneNum] + del self.level.zoneEntId2zoneId[entId] + # reassign the zoneIds (we may not need to do this, if all of the + # other entities already have their correct zoneId...?) + self.privAssignZoneIds() + + def privAssignZoneIds(self): + """assign zoneIds from self.level.zoneIds, according to the zones + that are registered so far in self.level.zoneNum2entId""" + # sort the model zoneNums + modelZoneNums = self.level.zoneNum2entId.keys() + modelZoneNums.sort() + + # dole out the zoneIds, in increasing order of zoneNum + for i in range(len(modelZoneNums)): + zoneNum = modelZoneNums[i] + entId = self.level.zoneNum2entId[zoneNum] + zoneEnt = self.level.getEntity(entId) + zoneEnt.setZoneId(self.level.zoneIds[i]) + + # the zoneIds have shifted. update the tables + for entId in self.level.zoneNum2entId.values(): + zoneEnt = self.level.getEntity(entId) + zoneId = zoneEnt.getZoneId() + self.level.zoneNum2zoneId[zoneEnt.modelZoneNum] = zoneId + self.level.zoneEntId2zoneId[entId] = zoneId diff --git a/direct/src/level/LevelMgrAI.py b/direct/src/level/LevelMgrAI.py index d82504d35f..678657b1a5 100755 --- a/direct/src/level/LevelMgrAI.py +++ b/direct/src/level/LevelMgrAI.py @@ -1,10 +1,68 @@ """LevelMgrAI module: contains the LevelMgrAI class""" -import Entity +from PythonUtil import Functor +import LevelMgrBase -class LevelMgrAI(Entity.Entity): +class LevelMgrAI(LevelMgrBase.LevelMgrBase): """This class manages editable AI level attributes""" def __init__(self, level, entId): - Entity.Entity.__init__(self, level, entId) - self.level.levelMgr = self - + LevelMgrBase.LevelMgrBase.__init__(self, level, entId) + + # modelZoneNum -> zone entId + self.level.zoneNum2entId = {} + # modelZoneNum -> network zoneId + self.level.zoneNum2zoneId = {} + # zone entId -> network zoneId + self.level.zoneEntId2zoneId = {} + # list of network zoneIDs, sorted by modelZoneNum + self.level.zoneIds = [] + + # listen for every zone creation + self.accept(self.level.getEntityOfTypeCreateEvent('zone'), + self.handleZoneCreated) + + def destroy(self): + del self.level.zoneIds + del self.level.zoneEntId2zoneId + del self.level.zoneNum2zoneId + del self.level.zoneNum2entId + LevelMgrBase.LevelMgrBase.destroy(self) + + def handleZoneCreated(self, entId): + zoneEnt = self.level.getEntity(entId) + + # register the zone's info in the tables + self.level.zoneNum2entId[zoneEnt.modelZoneNum] = entId + self.level.zoneNum2zoneId[zoneEnt.modelZoneNum] = zoneEnt.getZoneId() + self.level.zoneEntId2zoneId[entId] = zoneEnt.getZoneId() + + # TODO: we should delay this until all zone entities have been + # created on level init + self.privCreateSortedZoneIdList() + + # listen for the zone's destruction + self.accept(self.level.getEntityDestroyEvent(entId), + Functor(self.handleZoneDestroy, entId)) + + def handleZoneDestroy(self, entId): + zoneEnt = self.level.getEntity(entId) + # unregister the zone from the maps + del self.level.zoneNum2entId[zoneEnt.modelZoneNum] + del self.level.zoneNum2zoneId[zoneEnt.modelZoneNum] + del self.level.zoneEntId2zoneId[entId] + # recreate the sorted network zoneId list + self.privCreateSortedZoneIdList() + + def privCreateSortedZoneIdList(self): + # sort the model zoneNums + modelZoneNums = self.level.zoneNum2entId.keys() + modelZoneNums.sort() + + # create a list of network zoneIds, ordered by their corresponding + # sorted model zoneNum values + self.level.zoneIds = [] + for zoneNum in modelZoneNums: + self.level.zoneIds.append(self.level.zoneNum2zoneId[zoneNum]) + + # TODO: if we just added or removed a zone entity AFTER the level + # was initialized, we need to re-send the zoneId list diff --git a/direct/src/level/LevelMgrBase.py b/direct/src/level/LevelMgrBase.py new file mode 100755 index 0000000000..0306591ad2 --- /dev/null +++ b/direct/src/level/LevelMgrBase.py @@ -0,0 +1,14 @@ +"""LevelMgrBase module: contains the LevelMgrBase class""" + +import Entity + +class LevelMgrBase(Entity.Entity): + """This class contains LevelMgr code shared by the AI and client""" + def __init__(self, level, entId): + Entity.Entity.__init__(self, level, entId) + self.level.levelMgr = self + + def destroy(self): + del self.level.levelMgr + Entity.Entity.destroy(self) + self.ignoreAll() diff --git a/direct/src/level/ZoneEntity.py b/direct/src/level/ZoneEntity.py index fb68d4e884..ca13636032 100755 --- a/direct/src/level/ZoneEntity.py +++ b/direct/src/level/ZoneEntity.py @@ -1,11 +1,11 @@ """ZoneEntity module: contains the ZoneEntity class""" -import Entity +import ZoneEntityBase import BasicEntities -class ZoneEntity(Entity.Entity, BasicEntities.NodePathAttribs): +class ZoneEntity(ZoneEntityBase.ZoneEntityBase, BasicEntities.NodePathAttribs): def __init__(self, level, entId): - Entity.Entity.__init__(self, level, entId) + ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId) self.nodePath = self.level.getZoneNode(self.modelZoneNum) self.initNodePathAttribs(doReparent=0) diff --git a/direct/src/level/ZoneEntityAI.py b/direct/src/level/ZoneEntityAI.py index c76f598456..c8609dd293 100755 --- a/direct/src/level/ZoneEntityAI.py +++ b/direct/src/level/ZoneEntityAI.py @@ -1,9 +1,21 @@ """ZoneEntityAI module: contains the ZoneEntityAI class""" -import Entity +import ZoneEntityBase -class ZoneEntityAI(Entity.Entity): +class ZoneEntityAI(ZoneEntityBase.ZoneEntityBase): def __init__(self, level, entId): - Entity.Entity.__init__(self, level, entId) - # allocate a network zoneId for each zone - self.level.allocateZoneId() + ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId) + + if self.isUberZone(): + print 'uberZone' + # the uberzone is already allocated + self.setZoneId(self.level.uberZoneId) + else: + # allocate a network zoneId for this zone + # there is error checking in air.allocateZone + self.setZoneId(self.level.air.allocateZone()) + + def destroy(self): + if not self.isUberZone(): + self.level.air.deallocateZone(self.getZoneId()) + ZoneEntityBase.ZoneEntityBase.destroy(self) diff --git a/direct/src/level/ZoneEntityBase.py b/direct/src/level/ZoneEntityBase.py new file mode 100755 index 0000000000..6b5e783729 --- /dev/null +++ b/direct/src/level/ZoneEntityBase.py @@ -0,0 +1,23 @@ +"""ZoneEntityBase module: contains the ZoneEntityBase class""" + +import Entity +import Level + +class ZoneEntityBase(Entity.Entity): + def __init__(self, level, entId): + Entity.Entity.__init__(self, level, entId) + self.zoneId = None + + def destroy(self): + del self.zoneId + Entity.Entity.destroy(self) + + def isUberZone(self): + return self.entId == Level.Level.UberZoneEntId + + def setZoneId(self, zoneId): + """set the network zoneId that this zone entity corresponds to""" + self.zoneId = zoneId + + def getZoneId(self): + return self.zoneId