code reorg; handling of dynamic zone entity creation and deletion

This commit is contained in:
Darren Ranalli 2003-09-24 01:29:08 +00:00
parent d62dc09acf
commit 4b4c1e3a62
10 changed files with 246 additions and 104 deletions

View File

@ -40,7 +40,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
# all our entities at that time. # all our entities at that time.
toonbase.tcr.timeManager.synchronize('DistributedLevel.generate') 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 # the AI level obj doesn't know the data values until it has been
# generated.) # generated.)
def setZoneIds(self, zoneIds): def setZoneIds(self, zoneIds):
@ -92,8 +92,6 @@ class DistributedLevel(DistributedObject.DistributedObject,
# similar handlers in base classes # similar handlers in base classes
if entType == 'levelMgr': if entType == 'levelMgr':
self.__handleLevelMgrCreated() self.__handleLevelMgrCreated()
elif entType == 'zone':
self.__handleAllZonesCreated()
def __handleLevelMgrCreated(self): 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
@ -129,20 +127,9 @@ class DistributedLevel(DistributedObject.DistributedObject,
self.zoneNums.sort() self.zoneNums.sort()
self.notify.debug('zones: %s' % self.zoneNums) self.notify.debug('zones: %s' % self.zoneNums)
# hack in another doorway # fix up the floor collisions for walkable zones *before*
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
# any entities get put under the model # any entities get put under the model
for zoneNum in self.zoneNums: for zoneNum,zoneNode in self.zoneNum2node.items():
zoneNode = self.zoneNum2node[zoneNum]
# if this is a walkable zone, fix up the model # if this is a walkable zone, fix up the model
allColls = zoneNode.findAllMatches('**/+CollisionNode').asList() allColls = zoneNode.findAllMatches('**/+CollisionNode').asList()
# which of them, if any, are floors? # which of them, if any, are floors?
@ -170,6 +157,14 @@ class DistributedLevel(DistributedObject.DistributedObject,
self.toonEnterZone(zoneNum) self.toonEnterZone(zoneNum)
self.accept('enter%s' % floorCollName, handleZoneEnter) 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): def announceGenerate(self):
self.notify.debug('announceGenerate') self.notify.debug('announceGenerate')
DistributedObject.DistributedObject.announceGenerate(self) DistributedObject.DistributedObject.announceGenerate(self)
@ -270,7 +265,9 @@ class DistributedLevel(DistributedObject.DistributedObject,
# if no viz, listen to all the zones # if no viz, listen to all the zones
if not DistributedLevel.WantVisibility: if not DistributedLevel.WantVisibility:
self.setVisibility(self.zoneNums) zoneNums = list(self.zoneNums)
zoneNums.remove(Level.Level.uberZoneNum)
self.setVisibility(zoneNums)
def toonEnterZone(self, zoneNum): def toonEnterZone(self, zoneNum):
self.notify.debug('toonEnterZone%s' % zoneNum) self.notify.debug('toonEnterZone%s' % zoneNum)
@ -328,7 +325,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
# accepts list of visible zone numbers # accepts list of visible zone numbers
# convert the zone numbers into their actual zoneIds # convert the zone numbers into their actual zoneIds
# always include Toontown and factory uberZones # always include Toontown and factory uberZones
factoryUberZone = self.getZoneId(zoneNum=0) factoryUberZone = self.getZoneId(zoneNum=Level.Level.UberZoneNum)
visibleZoneIds = [ToontownGlobals.UberZone, factoryUberZone] visibleZoneIds = [ToontownGlobals.UberZone, factoryUberZone]
for vz in vizList: for vz in vizList:
visibleZoneIds.append(self.getZoneId(zoneNum=vz)) visibleZoneIds.append(self.getZoneId(zoneNum=vz))

View File

@ -30,11 +30,6 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
def delete(self): def delete(self):
self.notify.debug('delete') self.notify.debug('delete')
self.destroyLevel() 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) DistributedObjectAI.DistributedObjectAI.delete(self)
def initializeLevel(self, spec): def initializeLevel(self, spec):
@ -48,9 +43,6 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
scenario = wc.choose() scenario = wc.choose()
scenarioIndex = spec['scenarios'].index(scenario) scenarioIndex = spec['scenarios'].index(scenario)
# this will hold the network zoneIds that we allocate
self.zoneIds = [self.uberZoneId]
Level.Level.initializeLevel(self, self.doId, Level.Level.initializeLevel(self, self.doId,
spec, scenarioIndex) spec, scenarioIndex)
@ -59,30 +51,23 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
Inheritors, override if desired.""" Inheritors, override if desired."""
return EntityCreatorAI.EntityCreatorAI(self.air, level=self) 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): def getEntityZoneId(self, entId):
"""figure out what network zoneId an entity is in""" """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 # 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] spec = self.entId2spec[entId]
type = spec['type'] type = spec['type']
if type == 'zone': if type == 'zone':
if not hasattr(self, 'zoneNum2zoneId'): 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 # we have no idea yet which zoneNums map to which
# network zoneIds. just return None. # network zoneIds. just return None.
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'): if not spec.has_key('parent'):
return None return None
return self.getEntityZoneId(spec['parent']) return self.getEntityZoneId(spec['parent'])

View File

@ -1,10 +1,11 @@
"""Entity.py: contains the Entity class""" """Entity.py: contains the Entity class"""
from DirectObject import *
from PythonUtil import lineInfo
import string import string
import DirectNotifyGlobal import DirectNotifyGlobal
from PythonUtil import lineInfo
class Entity: class Entity(DirectObject):
"""Entity is the base class for all objects that exist in a Level """Entity is the base class for all objects that exist in a Level
and can be edited with the LevelEditor.""" and can be edited with the LevelEditor."""
notify = DirectNotifyGlobal.directNotify.newCategory('Entity') notify = DirectNotifyGlobal.directNotify.newCategory('Entity')
@ -32,6 +33,7 @@ class Entity:
return 'ent%s(%s)' % (self.entId, self.level.getEntityType(self.entId)) return 'ent%s(%s)' % (self.entId, self.level.getEntityType(self.entId))
def destroy(self): def destroy(self):
self.level.onEntityDestroy(self.entId)
del self.level del self.level
def privGetSetter(self, attrib): def privGetSetter(self, attrib):
@ -40,6 +42,15 @@ class Entity:
return getattr(self, setFuncName) return getattr(self, setFuncName)
return None 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): def privCallSetters(self, doDelete, *attribs):
"""common implementation of callSetters and callSettersAndDelete""" """common implementation of callSetters and callSettersAndDelete"""
for attrib in attribs: for attrib in attribs:
@ -51,15 +62,6 @@ class Entity:
delattr(self, attrib) delattr(self, attrib)
setter(value) 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 # this will be called with each item of our spec data on initialization
def setAttribInit(self, attrib, value): def setAttribInit(self, attrib, value):
if hasattr(self, attrib): if hasattr(self, attrib):

View File

@ -30,6 +30,7 @@ class Level:
entities and their interrelations, and creates and destroys entities""" entities and their interrelations, and creates and destroys entities"""
notify = DirectNotifyGlobal.directNotify.newCategory('Level') notify = DirectNotifyGlobal.directNotify.newCategory('Level')
UberZoneNum = 0
UberZoneEntId = 0 UberZoneEntId = 0
def __init__(self): def __init__(self):
@ -61,10 +62,13 @@ class Level:
# there should be one and only one levelMgr # there should be one and only one levelMgr
assert len(self.entType2ids['levelMgr']) == 1 assert len(self.entType2ids['levelMgr']) == 1
# make sure the uberzone is there
assert Level.UberZoneEntId in self.entType2ids['zone']
# get an entity creator object # get an entity creator object
self.entityCreator = self.createEntityCreator() self.entityCreator = self.createEntityCreator()
# create all the entities # create all the entities
# TODO: we should leave this to a subclass or the level user
self.createAllEntities(priorityTypes=['levelMgr','zone']) self.createAllEntities(priorityTypes=['levelMgr','zone'])
def destroyLevel(self): def destroyLevel(self):
@ -132,6 +136,13 @@ class Level:
# below. # below.
if entity is not None: if entity is not None:
self.createdEntities.append(entity) 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 return entity
def initializeEntity(self, entity): def initializeEntity(self, entity):
@ -150,11 +161,8 @@ class Level:
# entity is initialized, add it to the list of entities # entity is initialized, add it to the list of entities
self.entities[entity.entId] = entity self.entities[entity.entId] = entity
# call the create handler
self.onEntityCreate(entId)
def getEntity(self, entId): def getEntity(self, entId):
return self.entities[entId] return self.entities.get(entId)
def getEntityType(self, entId): def getEntityType(self, entId):
return self.entId2spec[entId]['type'] return self.entId2spec[entId]['type']
@ -213,8 +221,6 @@ class Level:
messenger.send(self.getEntityTypePreCreateEvent(entType)) messenger.send(self.getEntityTypePreCreateEvent(entType))
def onEntityTypePostCreate(self, entType): def onEntityTypePostCreate(self, entType):
"""Level has just created these entities""" """Level has just created these entities"""
if entType == 'zone':
self.__handleAllZonesCreated()
messenger.send(self.getEntityTypePostCreateEvent(entType)) messenger.send(self.getEntityTypePostCreateEvent(entType))
# ENTITY # ENTITY
def onEntityCreate(self, entId): def onEntityCreate(self, entId):
@ -226,31 +232,12 @@ class Level:
self.getEntityOfTypeCreateEvent(self.getEntityType(entId)), self.getEntityOfTypeCreateEvent(self.getEntityType(entId)),
[entId]) [entId])
def __handleAllZonesCreated(self): # these are events and handlers that are invoked as entities are destroyed
"""once all the zone entities have been created, and we've got a def getEntityDestroyEvent(self, entId):
list of zoneIds in self.zoneIds, init zone tables""" """This is the event that is thrown immediately before an
# create a table mapping the model's zone numbers to the zone entity is destroyed"""
# entIds; zone entities are tied to model zone nums in the level spec, return 'entityDestroy-%s-%s' % (self.levelId, entId)
# this is just for convenient lookup def onEntityDestroy(self, entId):
self.zoneNum2entId = {} """Level is about to destroy this entity"""
for entId in self.entType2ids['zone']: # send the entity-destroy event
zoneEnt = self.getEntity(entId) messenger.send(self.getEntityDestroyEvent(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

@ -1,18 +1,82 @@
"""LevelMgr module: contains the LevelMgr class""" """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""" """This class manages editable client-side level attributes"""
def __init__(self, level, entId): def __init__(self, level, entId):
Entity.Entity.__init__(self, level, entId) LevelMgrBase.LevelMgrBase.__init__(self, level, entId)
self.level.levelMgr = self
# load the model # load the model
self.geom = loader.loadModel(self.modelFilename) 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): 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() self.geom.removeNode()
del self.geom 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

View File

@ -1,10 +1,68 @@
"""LevelMgrAI module: contains the LevelMgrAI class""" """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""" """This class manages editable AI level attributes"""
def __init__(self, level, entId): def __init__(self, level, entId):
Entity.Entity.__init__(self, level, entId) LevelMgrBase.LevelMgrBase.__init__(self, level, entId)
self.level.levelMgr = self
# 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

View File

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

View File

@ -1,11 +1,11 @@
"""ZoneEntity module: contains the ZoneEntity class""" """ZoneEntity module: contains the ZoneEntity class"""
import Entity import ZoneEntityBase
import BasicEntities import BasicEntities
class ZoneEntity(Entity.Entity, BasicEntities.NodePathAttribs): class ZoneEntity(ZoneEntityBase.ZoneEntityBase, BasicEntities.NodePathAttribs):
def __init__(self, level, entId): 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.nodePath = self.level.getZoneNode(self.modelZoneNum)
self.initNodePathAttribs(doReparent=0) self.initNodePathAttribs(doReparent=0)

View File

@ -1,9 +1,21 @@
"""ZoneEntityAI module: contains the ZoneEntityAI class""" """ZoneEntityAI module: contains the ZoneEntityAI class"""
import Entity import ZoneEntityBase
class ZoneEntityAI(Entity.Entity): class ZoneEntityAI(ZoneEntityBase.ZoneEntityBase):
def __init__(self, level, entId): def __init__(self, level, entId):
Entity.Entity.__init__(self, level, entId) ZoneEntityBase.ZoneEntityBase.__init__(self, level, entId)
# allocate a network zoneId for each zone
self.level.allocateZoneId() 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)

View File

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