zones are now entities, and lots of other tweaks

This commit is contained in:
Darren Ranalli 2003-09-19 01:02:10 +00:00
parent ad238789c3
commit 6e1f293140
16 changed files with 501 additions and 333 deletions

View File

@ -4,7 +4,36 @@ import Entity
import DistributedEntity
import NodePath
# this is an internal class, do not instantiate.
# this is an abstract class, do not instantiate.
class NodePathAttribs:
"""Derive from this class to give an entity the behavior of a
NodePath, without necessarily deriving from NodePath. Derived class
must implement getNodePath()."""
def initNodePathAttribs(self, doReparent=1):
"""Call this after the entity has been initialized"""
self.callSetters('pos','x','y','z',
'hpr','h','p','r',
'scale','sx','sy','sz')
if doReparent and hasattr(self, 'parent'):
self.level.requestReparent(self.getNodePath(), self.parent)
def reparentTo(self, *args): self.getNodePath().reparentTo(*args)
def setPos(self, *args): self.getNodePath().setPos(*args)
def setX(self, *args): self.getNodePath().setX(*args)
def setY(self, *args): self.getNodePath().setY(*args)
def setZ(self, *args): self.getNodePath().setZ(*args)
def setHpr(self, *args): self.getNodePath().setHpr(*args)
def setH(self, *args): self.getNodePath().setH(*args)
def setP(self, *args): self.getNodePath().setP(*args)
def setR(self, *args): self.getNodePath().setR(*args)
def setScale(self, *args): self.getNodePath().setScale(*args)
def setSx(self, *args): self.getNodePath().setSx(*args)
def setSy(self, *args): self.getNodePath().setSy(*args)
def setSz(self, *args): self.getNodePath().setSz(*args)
class privNodePathImpl(NodePath.NodePath):
def __init__(self, name):
node = hidden.attachNewNode(name)
@ -15,33 +44,32 @@ class privNodePathImpl(NodePath.NodePath):
of its attributes have been set"""
self.callSetters('pos','x','y','z',
'hpr','h','p','r',
'scale','sx','sx','sz')
if hasattr(self, 'parent'):
self.level.requestReparent(self, self.parent)
else:
self.level.requestReparent(self, 'zone')
'scale','sx','sy','sz')
self.level.requestReparent(self, self.parent)
def destroy(self):
self.removeNode()
def getNodePath(self):
return self
class NodePathEntity(Entity.Entity, privNodePathImpl):
"""This is an entity that represents a NodePath on the client.
It may be instantiated directly or used as a base class for other
entity types."""
def __init__(self, level, entId):
privNodePathImpl.__init__(self, '')
Entity.Entity.__init__(self, level, entId)
privNodePathImpl.__init__(self, str(self))
self.initializeEntity()
def initializeEntity(self):
Entity.Entity.initializeEntity(self)
self.setName(str(self))
privNodePathImpl.initNodePathAttribs(self)
def destroy(self):
Entity.Entity.destroy(self)
privNodePathImpl.destroy(self)
def getNodePath(self):
return self
class DistributedNodePathEntity(DistributedEntity.DistributedEntity,
privNodePathImpl):
"""This is a distributed version of NodePathEntity. It should not
@ -50,11 +78,14 @@ class DistributedNodePathEntity(DistributedEntity.DistributedEntity,
def __init__(self, cr):
DistributedEntity.DistributedEntity.__init__(self, cr)
privNodePathImpl.__init__(self, 'DistributedNodePathEntity')
def initializeEntity(self):
DistributedEntity.DistributedEntity.initializeEntity(self)
privNodePathImpl.initNodePathAttribs(self)
def announceGenerate(self):
DistributedEntity.DistributedEntity.announceGenerate(self)
privNodePathImpl.initNodePathAttribs(self)
def destroy(self):
DistributedEntity.DistributedEntity.destroy(self)
privNodePathImpl.destroy(self)
def getNodePath(self):
return self

View File

@ -32,8 +32,6 @@ class DistributedEntity(DistributedObject.DistributedObject, Entity.Entity):
# ask our level obj for our spec data
level = toonbase.tcr.doId2do[self.levelDoId]
Entity.Entity.__init__(self, level, self.entId)
# this sets all of our initial spec parameters on ourselves
self.initializeEntity()
DistributedObject.DistributedObject.announceGenerate(self)

View File

@ -7,13 +7,10 @@ class DistributedEntityAI(DistributedObjectAI.DistributedObjectAI,
notify = DirectNotifyGlobal.directNotify.newCategory(
'DistributedEntityAI')
def __init__(self, air, levelDoId, entId):
def __init__(self, air, level, entId):
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
self.levelDoId = levelDoId
level = self.air.doId2do[self.levelDoId]
self.levelDoId = level.doId
Entity.Entity.__init__(self, level, entId)
# get our spec data
self.initializeEntity()
def generate(self):
self.notify.debug('generate')

View File

@ -24,13 +24,13 @@ class DistributedInteractiveEntityAI(DistributedEntityAI.DistributedEntityAI):
if __debug__:
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedInteractiveEntityAI')
def __init__(self, air, levelDoId, entId):
def __init__(self, air, level, entId):
"""entId: a unique identifier for this prop."""
DistributedEntityAI.DistributedEntityAI.__init__(self, air,
levelDoId, entId)
level, entId)
assert(self.debugPrint(
"DistributedInteractiveEntityAI(air=%s, entId=%s)"
%("the air", entId)))
"DistributedInteractiveEntityAI(entId=%s)"
%(entId)))
self.fsm = FSM.FSM('DistributedInteractiveEntityAI',
[State.State('off',
self.enterOff,

View File

@ -13,7 +13,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
"""DistributedLevel"""
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel')
WantVisibility = config.GetBool('level-visibility', 0)
WantVisibility = config.GetBool('level-visibility', 1)
HideZones = config.GetBool('level-hidezones', 1)
def __init__(self, cr):
@ -37,7 +37,9 @@ class DistributedLevel(DistributedObject.DistributedObject,
# all our entities at that time.
toonbase.tcr.timeManager.synchronize('DistributedLevel.generate')
# required fields
# 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):
self.notify.debug('setZoneIds: %s' % zoneIds)
self.zoneIds = zoneIds
@ -48,22 +50,73 @@ 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
# levelAnnounceGenerate() in the last 'faux-required' DC update
# handler. If you add another field, move this to the last one.
self.levelAnnounceGenerate()
def levelAnnounceGenerate(self):
pass
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)
self.localEntities = {}
# get list of entity types we need to create
entTypes = self.entType2Ids.keys()
# create the levelMgr
self.levelMgr = self.createEntity(self.entType2Ids['levelMgr'][0])
entTypes.remove('levelMgr')
# there should not be any pending reparents left at this point
assert len(self.parent2ChildIds) == 0
# make sure the zoneNums from the model match the zoneNums from
# the zone entities
assert sameElements(self.zoneNums, self.zoneNum2entId.keys())
# load stuff
# fix up the floor collisions for walkable zones
for zoneNum in self.zoneNums:
zoneNode = self.zoneNum2node[zoneNum]
# if this is a walkable zone, fix up the model
floorColls = zoneNode.findAllMatches('**/+CollisionNode').asList()
if len(floorColls) > 0:
# rename the floor collision nodes, and make sure no other
# nodes under the ZoneNode have that name
floorCollName = '%s' % zoneNum
others = zoneNode.findAllMatches(
'**/%s' % floorCollName).asList()
for other in others:
other.setName('%s_renamed' % floorCollName)
for floorColl in floorColls:
floorColl.setName(floorCollName)
# listen for zone enter events from floor collisions
def handleZoneEnter(collisionEntry,
self=self, zoneNum=zoneNum):
# eat the collisionEntry
self.toonEnterZone(zoneNum)
self.accept('enter%s' % floorCollName, handleZoneEnter)
self.initVisibility()
def createEntityCreator(self):
"""Create the object that will be used to create Entities.
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)
def removeEntityCreationHandlers(self):
LevelBase.LevelBase.removeEntityCreationHandlers(self)
def handleLevelMgrCreated(self):
# as soon as the levelMgr has been created, load up the model
# and extract zone info
self.geom = loader.loadModel(self.modelFilename)
def findNumberedNodes(baseString, model=self.geom, self=self):
@ -87,50 +140,13 @@ class DistributedLevel(DistributedObject.DistributedObject,
return num2node
# find the zones in the model and fix them up
self.zoneNum2Node = findNumberedNodes('Zone')
self.zoneNum2node = findNumberedNodes('Zone')
# add the UberZone to the table
self.zoneNum2Node[0] = self.geom
self.zoneNum2node[0] = self.geom
self.zoneNums = self.zoneNum2Node.keys()
# take the UberZone out of the list
self.zoneNums.remove(0)
self.zoneNums = self.zoneNum2node.keys()
self.zoneNums.sort()
self.notify.debug('zones: %s' % self.zoneNums)
assert sameElements(self.zoneNums, self.spec['zones'].keys())
# fix up the floor collisions for walkable zones
for zoneNum in self.zoneNums:
zoneNode = self.zoneNum2Node[zoneNum]
# if this is a walkable zone, fix up the model
floorColls = zoneNode.findAllMatches('**/+CollisionNode').asList()
if len(floorColls) > 0:
# rename the floor collision nodes, and make sure no other
# nodes under the ZoneNode have that name
floorCollName = '%s' % zoneNum
others = zoneNode.findAllMatches(
'**/%s' % floorCollName).asList()
for other in others:
other.setName('%s_renamed' % floorCollName)
for floorColl in floorColls:
floorColl.setName(floorCollName)
# listen for zone enter events from floor collisions
def handleZoneEnter(collisionEntry,
self=self, zoneNum=zoneNum):
# eat the collisionEntry
self.toonEnterZone(zoneNum)
self.accept('enter%s' % floorCollName, handleZoneEnter)
# listen for camera-ray/floor collision events
def handleCameraRayFloorCollision(collEntry, self=self):
name = collEntry.getIntoNode().getName()
try:
zoneNum = int(name)
self.camEnterZone(zoneNum)
except:
self.notify.warning('Invalid floor collision node: %s' % name)
self.accept('on-floor', handleCameraRayFloorCollision)
# hack in another doorway
dw = self.geom.attachNewNode('Doorway27')
@ -140,48 +156,16 @@ class DistributedLevel(DistributedObject.DistributedObject,
# find the doorway nodes
self.doorwayNum2Node = findNumberedNodes('Doorway')
self.initVisibility()
# create the rest of the client-side Entities
# TODO: only create client-side Entities for the
# currently-visible zones?
for entType in entTypes:
for entId in self.entType2Ids[entType]:
self.createEntity(entId)
# there should not be any pending reparents left at this point
assert len(self.parent2ChildIds) == 0
def makeEntityCreator(self):
"""Create the object that will be used to create Entities.
Inheritors, override if desired."""
return EntityCreator.EntityCreator()
def createEntity(self, entId):
assert not self.localEntities.has_key(entId)
spec = self.entId2Spec[entId]
self.notify.debug('creating %s %s' % (spec['type'], entId))
entity = self.entityCreator.createEntity(spec['type'], self, entId)
if entity is not None:
self.localEntities[entId] = entity
return entity
def announceGenerate(self):
self.notify.debug('announceGenerate')
DistributedObject.DistributedObject.announceGenerate(self)
def disable(self):
self.notify.debug('disable')
self.destroyLevel()
DistributedObject.DistributedObject.disable(self)
self.ignoreAll()
# destroy all of the local entities
for entId, entity in self.localEntities.items():
entity.destroy()
del self.localEntities
self.destroyLevel()
def delete(self):
self.notify.debug('delete')
DistributedObject.DistributedObject.delete(self)
@ -190,67 +174,79 @@ class DistributedLevel(DistributedObject.DistributedObject,
# returns node that doors should parent themselves to
return self.doorwayNum2Node[doorwayNum]
def requestReparent(self, entity, parent):
if parent is 'zone':
entity.reparentTo(self.zoneNum2Node[entity.zone])
def getZoneNode(self, zoneNum):
return self.zoneNum2node[zoneNum]
def requestReparent(self, entity, parentId):
assert(entity.entId != parentId)
if self.entities.has_key(parentId):
# parent has already been created
entity.reparentTo(self.entities[parentId].getNodePath())
else:
parentId = parent
assert(entity.entId != parentId)
# parent hasn't been created yet; schedule the reparent
self.notify.debug(
'entity %s requesting reparent to %s, not yet created' %
(entity, parentId))
if self.entities.has_key(parentId):
# parent has already been created
entity.reparentTo(self.entities[parentId])
else:
# parent hasn't been created yet; schedule the reparent
self.notify.debug(
'entity %s requesting reparent to %s, not yet created' %
(entity, parent))
entId = entity.entId
entity.reparentTo(hidden)
entId = entity.entId
entity.reparentTo(hidden)
# if this parent doesn't already have another child pending,
# do some setup
if not self.parent2ChildIds.has_key(parentId):
self.parent2ChildIds[parentId] = []
# if this parent doesn't already have another child pending,
# do some setup
if not self.parent2ChildIds.has_key(parentId):
self.parent2ChildIds[parentId] = []
# do the reparent once the parent is initialized
def doReparent(parentId=parentId, self=self):
assert self.parent2ChildIds.has_key(parentId)
parent=self.getEntity(parentId)
for entId in self.parent2ChildIds[parentId]:
entity=self.getEntity(entId)
self.notify.debug(
'performing pending reparent of %s to %s' %
(entity, parent))
entity.reparentTo(parent.getNodePath())
del self.parent2ChildIds[parentId]
self.accept(self.getEntityCreateEvent(parentId), doReparent)
# do the reparent once the parent is initialized
def doReparent(parentId=parentId, self=self):
assert self.parent2ChildIds.has_key(parentId)
parent=self.getEntity(parentId)
for entId in self.parent2ChildIds[parentId]:
entity=self.getEntity(entId)
self.notify.debug(
'performing pending reparent of %s to %s' %
(entity, parent))
entity.reparentTo(parent)
del self.parent2ChildIds[parentId]
self.accept(self.getEntityCreateEvent(parentId), doReparent)
self.parent2ChildIds[parentId].append(entId)
self.parent2ChildIds[parentId].append(entId)
def showZone(self, zoneNum):
self.zoneNum2Node[zoneNum].show()
self.zoneNum2node[zoneNum].show()
def hideZone(self, zoneNum):
self.zoneNum2Node[zoneNum].hide()
self.zoneNum2node[zoneNum].hide()
def setTransparency(self, alpha, zone=None):
self.geom.setTransparency(1)
if zone is None:
node = self.geom
else:
node = self.zoneNum2Node[zone]
node = self.zoneNum2node[zone]
node.setAlphaScale(alpha)
def initVisibility(self):
# start out with every zone visible, since none of the zones have
# been hidden
self.curVisibleZoneNums = list2dict(self.zoneNums)
# the UberZone is always visible, so it's not included in the
# viz lists
del self.curVisibleZoneNums[0]
# we have not entered any zone yet
self.curZoneNum = None
# listen for camera-ray/floor collision events
def handleCameraRayFloorCollision(collEntry, self=self):
name = collEntry.getIntoNode().getName()
try:
zoneNum = int(name)
except:
self.notify.warning('Invalid floor collision node: %s' % name)
else:
self.camEnterZone(zoneNum)
self.accept('on-floor', handleCameraRayFloorCollision)
# if no viz, listen to all the zones
if not DistributedLevel.WantVisibility:
self.setVisibility(self.zoneNums)
@ -270,8 +266,9 @@ class DistributedLevel(DistributedObject.DistributedObject,
if zoneNum == self.curZoneNum:
return
zoneSpec = self.spec['zones'][zoneNum]
zoneEntId = self.zoneNum2entId[zoneNum]
zoneSpec = self.entId2spec[zoneEntId]
# use dicts to efficiently ensure that there are no duplicates
visibleZoneNums = list2dict([zoneNum])
visibleZoneNums.update(list2dict(zoneSpec['visibility']))
@ -310,10 +307,10 @@ 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(0)
factoryUberZone = self.getZoneId(zoneNum=0)
visibleZoneIds = [ToontownGlobals.UberZone, factoryUberZone]
for vz in vizList:
visibleZoneIds.append(self.getZoneId(vz))
visibleZoneIds.append(self.getZoneId(zoneNum=vz))
assert(uniqueElements(visibleZoneIds))
self.notify.debug('new viz list: %s' % visibleZoneIds)

View File

@ -12,94 +12,24 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
"""DistributedLevelAI"""
notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI')
def __init__(self, air):
def __init__(self, air, zoneId):
DistributedObjectAI.DistributedObjectAI.__init__(self, air)
LevelBase.LevelBase.__init__(self)
self.uberZoneId = zoneId
def initializeLevel(self, spec, uberZoneId):
self.uberZoneId = uberZoneId
# We need a unique level ID to init the level system, and we need
# the level system to get the data for our required fields.
# Pre-allocate a doId.
self.preAllocateDoId()
# choose a scenario
wc = WeightedChoice.WeightedChoice(spec['scenarios'], 1)
scenario = wc.choose()
scenarioIndex = spec['scenarios'].index(scenario)
LevelBase.LevelBase.initializeLevel(self, self.doId,
spec, scenarioIndex)
self.aiEntities = {}
# get list of entity types we need to create
self.entTypes = self.entType2Ids.keys()
# create the levelMgr
self.levelMgr = self.createEntity(self.entType2Ids['levelMgr'][0])
self.entTypes.remove('levelMgr')
# allocate the rest of the zones; add one for the uber-zone
self.numZones = len(self.spec['zones']) + 1
self.zoneIds = [self.uberZoneId]
for i in range(1,self.numZones):
# there is error checking in air.allocateZone
self.zoneIds.append(self.air.allocateZone())
# record the level's start time so that we can sync the clients
self.startTime = globalClock.getRealTime()
self.startTimestamp = globalClockDelta.localToNetworkTime(
self.startTime, bits=32)
def makeEntityCreator(self):
"""Create the object that will be used to create Entities.
Inheritors, override if desired."""
return EntityCreatorAI.EntityCreatorAI()
def createEntity(self, entId):
assert not self.aiEntities.has_key(entId)
spec = self.entId2Spec[entId]
self.notify.debug('creating %s %s' % (spec['type'], entId))
zone = spec.get('zone')
# we might attempt to create non-distributed entities before
# we've been generated
if zone is not None:
zone = self.getZoneId(zone)
entity = self.entityCreator.createEntity(
spec['type'], self, entId, self.air, zone)
else:
entity = self.entityCreator.createEntity(
spec['type'], self, entId)
if entity is not None:
self.aiEntities[entId] = entity
return entity
# required-field getters
def getZoneIds(self):
return self.zoneIds
def getStartTimestamp(self):
return self.startTimestamp
def getScenarioIndex(self):
return self.scenarioIndex
def generate(self):
def generate(self, spec):
self.notify.debug('generate')
DistributedObjectAI.DistributedObjectAI.generate(self)
# create the rest of the Entities
for entType in self.entTypes:
for entId in self.entType2Ids[entType]:
self.createEntity(entId)
self.initializeLevel(spec)
self.sendUpdate('setZoneIds', [self.zoneIds])
self.sendUpdate('setStartTimestamp', [self.startTimestamp])
self.sendUpdate('setScenarioIndex', [self.scenarioIndex])
def delete(self):
self.notify.debug('delete')
for entId in self.aiEntities.keys():
self.aiEntities[entId].destroy()
del self.aiEntities
# we do not allocate the uberZone for now, so don't deallocate it
for zoneId in self.zoneIds[1:]:
self.air.deallocateZone(zoneId)
@ -108,6 +38,67 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
DistributedObjectAI.DistributedObjectAI.delete(self)
def initializeLevel(self, spec):
# record the level's start time so that we can sync the clients
self.startTime = globalClock.getRealTime()
self.startTimestamp = globalClockDelta.localToNetworkTime(
self.startTime, bits=32)
# choose a scenario
wc = WeightedChoice.WeightedChoice(spec['scenarios'], 1)
scenario = wc.choose()
scenarioIndex = spec['scenarios'].index(scenario)
# this will hold the network zoneIds that we allocate
self.zoneIds = [self.uberZoneId]
LevelBase.LevelBase.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
# 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
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 have no idea yet which zoneNums map to which
# network zoneIds. just return None.
return None
return self.zoneNum2zoneId[spec['modelZoneNum']]
if not spec.has_key('parent'):
return None
return self.getEntityZoneId(spec['parent'])
if __debug__:
# level editors should call this func to tweak attributes of level
# entities

View File

@ -25,14 +25,11 @@ class Entity:
if attribs is not None:
self.attribs.update(attribs)
self.level.initializeEntity(self)
def __str__(self):
return 'ent%s(%s)' % (self.entId, self.level.getEntityType(self.entId))
def initializeEntity(self):
"""Call this once on initialization to set this entity's
spec data"""
self.level.initializeEntity(self)
def destroy(self):
del self.level

View File

@ -1,42 +1,30 @@
"""EntityCreator module: contains the EntityCreator class"""
import EntityCreatorBase
import BasicEntities
import DirectNotifyGlobal
import LevelMgr
import ZoneEntity
# some useful constructor functions
# ctor functions must take (level, entId)
def nothing(level, entId):
def nothing(*args):
"""For entities that don't need to be created by the client"""
return None
class EntityCreator:
class EntityCreator(EntityCreatorBase.EntityCreatorBase):
"""This class is responsible for creating instances of Entities on the
client. It can be subclassed to handle more Entity types."""
notify = DirectNotifyGlobal.directNotify.newCategory('EntityCreator')
def __init__(self):
self.entType2Ctor = {}
def __init__(self, level):
EntityCreatorBase.EntityCreatorBase.__init__(self, level)
self.level = level
self.privRegisterTypes({
'levelMgr': LevelMgr.LevelMgr,
'logicGate': nothing,
'nodepath': BasicEntities.NodePathEntity,
'zone': ZoneEntity.ZoneEntity,
})
def privRegisterType(self, entType, ctor):
if self.entType2Ctor.has_key(entType):
EntityCreator.notify.warning(
'replacing %s ctor %s with %s' %
(entType, self.entType2Ctor[entType], ctor))
self.entType2Ctor[entType] = ctor
def privRegisterTypes(self, type2ctor):
for entType, ctor in type2ctor.items():
self.privRegisterType(entType, ctor)
def createEntity(self, entType, level, entId):
if not self.entType2Ctor.has_key(entType):
EntityCreator.notify.warning(
'createEntity(entType=%s): entType not found' % entType)
return None
return self.entType2Ctor[entType](level, entId)
def doCreateEntity(self, ctor, entId):
return ctor(self.level, entId)

View File

@ -1,59 +1,54 @@
"""EntityCreatorAI module: contains the EntityCreatorAI class"""
import DirectNotifyGlobal
import EntityCreatorBase
import LogicGateAI
import LevelMgrAI
import ZoneEntityAI
from PythonUtil import Functor
# some useful constructor functions
# ctor functions for distributed entities must take
# (air, level doId, entId, zoneId)
# ctor functions for non-distributed entities must take
# (level, entId)
def createDistributedEntity(AIclass, air, levelDoId, entId, zoneId):
# ctor functions for entities must take
# (air, level, entId, zoneId)
# this func creates distributed entities whose constructors take
# (air, level doId, entId)
# and do not generate themselves
def createDistributedEntity(AIclass, air, level, entId, zoneId):
"""create a distributed entity and call generate"""
ent = AIclass(air, levelDoId, entId)
ent = AIclass(air, level, entId)
ent.generateWithRequired(zoneId)
return ent
def nothing(air, levelDoId, entId, zoneId):
# this func creates local entities whose constructors take
# (level, entId)
def createLocalEntity(AIclass, air, level, entId, zoneId):
"""create a local entity"""
ent = AIclass(level, entId)
# take any number of args to support local and distributed entities
def nothing(*args):
"""Create entity that doesn't have a server side representation."""
return None
class EntityCreatorAI:
class EntityCreatorAI(EntityCreatorBase.EntityCreatorBase):
"""This class is responsible for creating instances of Entities on the AI.
It can be subclassed to handle more Entity types."""
notify = DirectNotifyGlobal.directNotify.newCategory('EntityCreatorAI')
def __init__(self):
self.entType2Ctor = {}
def __init__(self, air, level):
EntityCreatorBase.EntityCreatorBase.__init__(self, level)
self.air = air
# create short aliases for ctor funcs
cLE = createLocalEntity
self.privRegisterTypes({
'levelMgr': LevelMgrAI.LevelMgrAI,
'logicGate': LogicGateAI.LogicGateAI,
'levelMgr': Functor(cLE, LevelMgrAI.LevelMgrAI),
'logicGate': Functor(cLE, LogicGateAI.LogicGateAI),
'nodepath': nothing,
'zone': Functor(cLE, ZoneEntityAI.ZoneEntityAI),
})
def privRegisterType(self, entType, ctor):
if self.entType2Ctor.has_key(entType):
EntityCreatorAI.notify.warning(
'replacing %s ctor %s with %s' %
(entType, self.entType2Ctor[entType], ctor))
self.entType2Ctor[entType] = ctor
def privRegisterTypes(self, type2ctor):
for entType, ctor in type2ctor.items():
self.privRegisterType(entType, ctor)
def createEntity(self, entType, level, entId, air=None, zoneId=None):
"""zoneId=None indicates a non-distributed entity"""
if not self.entType2Ctor.has_key(entType):
EntityCreatorAI.notify.warning(
'createEntity(entType=%s, levelDoId=%s, '
'entId=%s, zoneId=%s) not found' %
(entType, level.doId, entId, zoneId))
return None
if zoneId is None:
return self.entType2Ctor[entType](level, entId)
else:
levelDoId = level.doId
return self.entType2Ctor[entType](air, levelDoId, entId, zoneId)
def doCreateEntity(self, ctor, entId):
zoneId = self.level.getEntityZoneId(entId)
self.notify.debug('creating entity %s in zone %s' % (entId, zoneId))
return ctor(self.air, self.level, entId, zoneId)

View File

@ -0,0 +1,33 @@
"""EntityCreatorBase module: contains the EntityCreatorBase class"""
import DirectNotifyGlobal
class EntityCreatorBase:
"""This class is responsible for creating instances of Entities on the
AI and on the client. It must be subclassed to specify what entity
types it can create, and to provide the creation implementation."""
notify = DirectNotifyGlobal.directNotify.newCategory('EntityCreator')
def __init__(self, level):
self.level = level
self.entType2Ctor = {}
def createEntity(self, entId):
entType = self.level.getEntityType(entId)
if not self.entType2Ctor.has_key(entType):
self.notify.error('unknown entity type: %s (ent%s)' %
(entType, entId))
# inheritor must define doCreateEntity
self.doCreateEntity(self.entType2Ctor[entType], entId)
def privRegisterType(self, entType, ctor):
if self.entType2Ctor.has_key(entType):
self.notify.warning('replacing %s ctor %s with %s' %
(entType, self.entType2Ctor[entType], ctor))
self.entType2Ctor[entType] = ctor
def privRegisterTypes(self, type2ctor):
for entType, ctor in type2ctor.items():
self.privRegisterType(entType, ctor)

View File

@ -4,15 +4,38 @@ 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 unchanging.
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 LevelBase:
"""LevelBase: shared client and AI code
representation of a game level, keeps track of all of the
Level Entities and their interrelations"""
notify = DirectNotifyGlobal.directNotify.newCategory('LevelBase')
def __init__(self, levelId=None):
if levelId is not None:
self.setLevelId(levelId)
UberZoneEntId = 0
def __init__(self):
pass
def initializeLevel(self, levelId, spec, scenarioIndex):
""" subclass should call this as soon as it has located
@ -24,47 +47,128 @@ class LevelBase:
# 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
entId2spec = {}
entId2spec.update(globalEntities)
entId2spec.update(scenarioEntities)
self.entId2spec = entId2spec
# create some handy tables
self.entType2Ids = {}
self.entZone2Ids = {}
self.nonZoneEntIds = []
for entId, spec in self.entId2Spec.items():
entType = spec['type']
self.entType2Ids.setdefault(entType, [])
self.entType2Ids[entType].append(entId)
entZone = spec.get('zone')
# note that entities with no Zone will be filed under 'None'
self.entZone2Ids.setdefault(entZone, [])
self.entZone2Ids[entZone].append(entId)
# 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
# this will be filled in as the entities are created and report in
self.entities = {}
assert len(self.entType2ids['levelMgr']) == 1
# get an entity creator object
self.entityCreator = self.makeEntityCreator()
self.entityCreator = self.createEntityCreator()
# set up handlers for entity creation
self.setupEntityCreationHandlers()
# create all the entities
self.createAllEntities(priorityTypes=['levelMgr','zone'])
# tear down the entity creation handlers
self.removeEntityCreationHandlers()
def makeEntityCreator(self):
def destroyLevel(self):
for entity in self.createdEntities:
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 destroyLevel(self):
del self.entities
del self.entId2Spec
del self.spec
def setupEntityCreationHandlers(self):
# set up any handlers for entity creation events
# override if desired, but be sure to call down
self.acceptOnce(
self.getEntityTypeCreateEvent('zone'),
self.handleAllZonesCreated)
def removeEntityCreationHandlers(self):
pass
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)
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/Entity 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 and 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
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()
# 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)
def createAllEntitiesOfType(self, entType):
"""creates all entities of a given type"""
assert entType in self.entType2ids
for entId in self.entType2ids[entType]:
self.createEntity(entId)
# send the entity type-create event
messenger.send(self.getEntityTypeCreateEvent(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)
if entity is not None:
self.createdEntities.append(entity)
return entity
def initializeEntity(self, entity):
"""populate an entity with its spec data"""
"""populate an entity with its spec data. This is not done
in createEntity in order to allow other pieces of code create
entities; this is called directly by Entity.
"""
entId = entity.entId
spec = self.entId2Spec[entId]
spec = self.entId2spec[entId]
# on initialization, set items directly on entity
for key,value in spec.items():
if key in ('type', 'name', 'comment',):
@ -73,8 +177,21 @@ class LevelBase:
# entity is initialized, add it to the list of entities
self.entities[entity.entId] = entity
# send the create event
# send the entity-create event
messenger.send(self.getEntityCreateEvent(entity.entId))
# send the entity-of-type create event
messenger.send(self.getEntityOfTypeCreateEvent(spec['type']),
[entId])
def getEntityTypeCreateEvent(self, entType):
"""This is the event that is thrown immediately after every
entity of a given type has been created"""
return 'entityTypeCreate-%s-%s' % (self.levelId, entType)
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)
def getEntityCreateEvent(self, entId):
"""This is the event that is thrown immediately after an entity
@ -85,9 +202,15 @@ class LevelBase:
return self.entities[entId]
def getEntityType(self, entId):
return self.entId2Spec[entId]['type']
return self.entId2spec[entId]['type']
def getZoneId(self, index):
# get the actual zoneId for this index
# TODO: perhaps the zones should be fixed up in the specs
return self.zoneIds[index]
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]

View File

@ -6,7 +6,6 @@ class LevelMgr(Entity.Entity):
"""This class manages editable client-side level attributes"""
def __init__(self, level, entId):
Entity.Entity.__init__(self, level, entId)
self.initializeEntity()
self.callSetters('modelFilename')
def setModelFilename(self, modelFilename):

View File

@ -6,4 +6,3 @@ class LevelMgrAI(Entity.Entity):
"""This class manages editable AI level attributes"""
def __init__(self, level, entId):
Entity.Entity.__init__(self, level, entId)
self.initializeEntity()

View File

@ -86,7 +86,6 @@ class LogicGateAI(Entity.Entity, PandaObject.PandaObject):
self.input1 = None
self.input2 = None
Entity.Entity.__init__(self, level, entId)
self.initializeEntity()
self.setLogicType(self.logicType)
self.setInput_input1_bool(self.input_input1_bool)
self.setInput_input2_bool(self.input_input2_bool)

14
direct/src/level/ZoneEntity.py Executable file
View File

@ -0,0 +1,14 @@
"""ZoneEntity module: contains the ZoneEntity class"""
import Entity
import BasicEntities
class ZoneEntity(Entity.Entity, BasicEntities.NodePathAttribs):
def __init__(self, level, entId):
Entity.Entity.__init__(self, level, entId)
self.nodePath = self.level.getZoneNode(self.modelZoneNum)
self.initNodePathAttribs(doReparent=0)
def getNodePath(self):
return self.nodePath

View File

@ -0,0 +1,7 @@
"""ZoneEntityAI module: contains the ZoneEntityAI class"""
import Entity
class ZoneEntityAI(Entity.Entity):
def __init__(self, level, entId):
Entity.Entity.__init__(self, level, entId)