LevelBase->Level, more robust entity creation handler system

This commit is contained in:
Darren Ranalli 2003-09-22 22:24:08 +00:00
parent 0f357ba02f
commit 326b4d8c78
4 changed files with 285 additions and 45 deletions

View File

@ -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:

View File

@ -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())

256
direct/src/level/Level.py Executable file
View File

@ -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

View File

@ -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()