Kaynağa Gözat

LevelBase->Level, more robust entity creation handler system

Darren Ranalli 22 yıl önce
ebeveyn
işleme
326b4d8c78

+ 19 - 25
direct/src/level/DistributedLevel.py

@@ -4,23 +4,24 @@ from ClockDelta import *
 from PythonUtil import Functor, sameElements, list2dict, uniqueElements
 from PythonUtil import Functor, sameElements, list2dict, uniqueElements
 import ToontownGlobals
 import ToontownGlobals
 import DistributedObject
 import DistributedObject
-import LevelBase
+import Level
 import DirectNotifyGlobal
 import DirectNotifyGlobal
 import EntityCreator
 import EntityCreator
 
 
 class DistributedLevel(DistributedObject.DistributedObject,
 class DistributedLevel(DistributedObject.DistributedObject,
-                       LevelBase.LevelBase):
+                       Level.Level):
     """DistributedLevel"""
     """DistributedLevel"""
     notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel')
     notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel')
 
 
     WantVisibility = config.GetBool('level-visibility', 1)
     WantVisibility = config.GetBool('level-visibility', 1)
     HideZones = config.GetBool('level-hidezones', 1)
     HideZones = config.GetBool('level-hidezones', 1)
 
 
+    # TODO: move level-model stuff to LevelMgr or FactoryLevelMgr?
     FloorCollPrefix = 'zoneFloor'
     FloorCollPrefix = 'zoneFloor'
 
 
     def __init__(self, cr):
     def __init__(self, cr):
         DistributedObject.DistributedObject.__init__(self, cr)
         DistributedObject.DistributedObject.__init__(self, cr)
-        LevelBase.LevelBase.__init__(self)
+        Level.Level.__init__(self)
 
 
     def generate(self):
     def generate(self):
         self.notify.debug('generate')
         self.notify.debug('generate')
@@ -52,6 +53,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
 
 
     def setScenarioIndex(self, scenarioIndex):
     def setScenarioIndex(self, scenarioIndex):
         self.scenarioIndex = scenarioIndex
         self.scenarioIndex = scenarioIndex
+
         # ugly hack: we treat these DC fields as if they were required,
         # ugly hack: we treat these DC fields as if they were required,
         # and use 'levelAnnounceGenerate()' in place of regular old
         # and use 'levelAnnounceGenerate()' in place of regular old
         # announceGenerate(). Note that we have to call
         # announceGenerate(). Note that we have to call
@@ -65,8 +67,8 @@ class DistributedLevel(DistributedObject.DistributedObject,
     def initializeLevel(self, spec):
     def initializeLevel(self, spec):
         """subclass should call this as soon as it's located its spec data.
         """subclass should call this as soon as it's located its spec data.
         Must be called after obj has been generated."""
         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.
         # all of the entities have been created now.
         # there should not be any pending reparents left at this point
         # there should not be any pending reparents left at this point
@@ -83,23 +85,17 @@ class DistributedLevel(DistributedObject.DistributedObject,
         Inheritors, override if desired."""
         Inheritors, override if desired."""
         return EntityCreator.EntityCreator(level=self)
         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 removeEntityCreationHandlers(self):
-        LevelBase.LevelBase.removeEntityCreationHandlers(self)
-
-    def handleLevelMgrCreated(self):
+    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 __handleLevelMgrCreated(self):
         # as soon as the levelMgr has been created, load up the model
         # as soon as the levelMgr has been created, load up the model
         # and extract zone info
         # and extract zone info
         self.geom = self.levelMgr.geom
         self.geom = self.levelMgr.geom
@@ -141,9 +137,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
         # find the doorway nodes
         # find the doorway nodes
         self.doorwayNum2Node = findNumberedNodes('Doorway')
         self.doorwayNum2Node = findNumberedNodes('Doorway')
 
 
-    def handleAllZonesCreated(self):
-        LevelBase.LevelBase.handleAllZonesCreated(self)
-        
+    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
         # any entities get put under the model
         for zoneNum in self.zoneNums:
         for zoneNum in self.zoneNums:

+ 9 - 21
direct/src/level/DistributedLevelAI.py

@@ -2,19 +2,19 @@
 
 
 from ClockDelta import *
 from ClockDelta import *
 import DistributedObjectAI
 import DistributedObjectAI
-import LevelBase
+import Level
 import DirectNotifyGlobal
 import DirectNotifyGlobal
 import EntityCreatorAI
 import EntityCreatorAI
 import WeightedChoice
 import WeightedChoice
 
 
 class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
 class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
-                         LevelBase.LevelBase):
+                         Level.Level):
     """DistributedLevelAI"""
     """DistributedLevelAI"""
     notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI')
     notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI')
 
 
     def __init__(self, air, zoneId):
     def __init__(self, air, zoneId):
         DistributedObjectAI.DistributedObjectAI.__init__(self, air)
         DistributedObjectAI.DistributedObjectAI.__init__(self, air)
-        LevelBase.LevelBase.__init__(self)
+        Level.Level.__init__(self)
         self.uberZoneId = zoneId
         self.uberZoneId = zoneId
 
 
     def generate(self, spec):
     def generate(self, spec):
@@ -29,13 +29,12 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
 
 
     def delete(self):
     def delete(self):
         self.notify.debug('delete')
         self.notify.debug('delete')
-        
+        self.destroyLevel()
+
         # we do not allocate the uberZone for now, so don't deallocate it
         # we do not allocate the uberZone for now, so don't deallocate it
         for zoneId in self.zoneIds[1:]:
         for zoneId in self.zoneIds[1:]:
             self.air.deallocateZone(zoneId)
             self.air.deallocateZone(zoneId)
 
 
-        self.destroyLevel()
-
         DistributedObjectAI.DistributedObjectAI.delete(self)
         DistributedObjectAI.DistributedObjectAI.delete(self)
 
 
     def initializeLevel(self, spec):
     def initializeLevel(self, spec):
@@ -52,27 +51,16 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
         # this will hold the network zoneIds that we allocate
         # this will hold the network zoneIds that we allocate
         self.zoneIds = [self.uberZoneId]
         self.zoneIds = [self.uberZoneId]
 
 
-        LevelBase.LevelBase.initializeLevel(self, self.doId,
-                                            spec, scenarioIndex)
+        Level.Level.initializeLevel(self, self.doId,
+                                    spec, scenarioIndex)
 
 
     def createEntityCreator(self):
     def createEntityCreator(self):
         """Create the object that will be used to create Entities.
         """Create the object that will be used to create Entities.
         Inheritors, override if desired."""
         Inheritors, override if desired."""
         return EntityCreatorAI.EntityCreatorAI(self.air, level=self)
         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
         # there is error checking in air.allocateZone
         self.zoneIds.append(self.air.allocateZone())
         self.zoneIds.append(self.air.allocateZone())
 
 

+ 256 - 0
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

+ 2 - 0
direct/src/level/ZoneEntityAI.py

@@ -5,3 +5,5 @@ import Entity
 class ZoneEntityAI(Entity.Entity):
 class ZoneEntityAI(Entity.Entity):
     def __init__(self, level, entId):
     def __init__(self, level, entId):
         Entity.Entity.__init__(self, level, entId)
         Entity.Entity.__init__(self, level, entId)
+        # allocate a network zoneId for each zone
+        self.level.allocateZoneId()