From cf7a1d14270a691ed2ceed56f15328bcebab71b7 Mon Sep 17 00:00:00 2001 From: Darren Ranalli Date: Fri, 24 Oct 2003 22:25:24 +0000 Subject: [PATCH] added 'permanent' entity types, fixed editor bugs --- direct/src/level/DistributedLevel.py | 15 +++++++++++-- direct/src/level/EditMgrAI.py | 2 +- direct/src/level/Entity.py | 7 ++++--- direct/src/level/EntityCreator.py | 3 ++- direct/src/level/EntityCreatorAI.py | 4 +++- direct/src/level/EntityCreatorBase.py | 4 +++- direct/src/level/EntityTypeDesc.py | 6 ++++++ direct/src/level/EntityTypeRegistry.py | 11 ++++++++++ direct/src/level/EntityTypes.py | 29 ++++++++++++++------------ direct/src/level/Level.py | 21 ++++++++++++------- direct/src/level/LevelMgrBase.py | 2 -- direct/src/level/LevelSpec.py | 22 +++++++++++-------- direct/src/level/LogicGateAI.py | 17 +++++++-------- direct/src/level/SpecUtil.py | 25 ++++++++++++++++++++++ 14 files changed, 119 insertions(+), 49 deletions(-) create mode 100755 direct/src/level/SpecUtil.py diff --git a/direct/src/level/DistributedLevel.py b/direct/src/level/DistributedLevel.py index 5524946a51..65cc4ea645 100755 --- a/direct/src/level/DistributedLevel.py +++ b/direct/src/level/DistributedLevel.py @@ -156,7 +156,12 @@ class DistributedLevel(DistributedObject.DistributedObject, 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()) + modelZoneNums = self.zoneNums + entityZoneNums = self.zoneNum2entId.keys() + if not sameElements(modelZoneNums, entityZoneNums): + DistributedLevel.notify.error( + 'model zone nums (%s) do not match entity zone nums (%s)' % + (modelZoneNums, entityZoneNums)) # load stuff self.initVisibility() @@ -177,7 +182,8 @@ class DistributedLevel(DistributedObject.DistributedObject, def __handleLevelMgrCreated(self): # as soon as the levelMgr has been created, load up the model # and extract zone info - self.geom = self.levelMgr.geom + levelMgr = self.getEntity(LevelConstants.LevelMgrEntId) + self.geom = levelMgr.geom def findNumberedNodes(baseString, model=self.geom, self=self): # finds nodes whose name follows the pattern 'baseString#' @@ -389,6 +395,11 @@ class DistributedLevel(DistributedObject.DistributedObject, if zoneNum == self.curZoneNum: return + if zoneNum not in self.zoneNum2entId: + DistributedLevel.notify.error( + 'no ZoneEntity for this zone (%s)!!' % zoneNum) + return + zoneEntId = self.zoneNum2entId[zoneNum] zoneSpec = self.levelSpec.getEntitySpec(zoneEntId) # use dicts to efficiently ensure that there are no duplicates diff --git a/direct/src/level/EditMgrAI.py b/direct/src/level/EditMgrAI.py index ae1f545f9c..136fb6f50e 100755 --- a/direct/src/level/EditMgrAI.py +++ b/direct/src/level/EditMgrAI.py @@ -15,7 +15,7 @@ class EditMgrAI(EditMgrBase.EditMgrBase): entIdDict = list2dict(entIds) # dumb linear search for now - # make this smarter (cache last-allocated id) + # TODO: make this smarter (cache last-allocated id) # Note that this uses the ID range associated with the # AI's username, not the username of the user who requested # the new entity. diff --git a/direct/src/level/Entity.py b/direct/src/level/Entity.py index 37d79a89ed..82866142fd 100755 --- a/direct/src/level/Entity.py +++ b/direct/src/level/Entity.py @@ -14,9 +14,10 @@ class Entity(DirectObject): self.initializeEntity(level, entId) def initializeEntity(self, level, entId): - """Distributed entities don't know their level or entId values - until they've been generated, so they call this after they've - been generated. At that point, the entity is good to go.""" + """Distributed entities on the client don't know their level or + entId values until they've been generated, so they call this + after they've been generated. At that point, the entity is good + to go.""" self.level = level self.entId = entId if (self.level is not None) and (self.entId is not None): diff --git a/direct/src/level/EntityCreator.py b/direct/src/level/EntityCreator.py index e0574d265b..314968e118 100755 --- a/direct/src/level/EntityCreator.py +++ b/direct/src/level/EntityCreator.py @@ -11,9 +11,10 @@ import ModelEntity # some useful constructor functions # ctor functions must take (level, entId) +# and they must return the entity that was created, or 'nothing' def nothing(*args): """For entities that don't need to be created by the client""" - return None + return 'nothing' class EntityCreator(EntityCreatorBase.EntityCreatorBase): """ diff --git a/direct/src/level/EntityCreatorAI.py b/direct/src/level/EntityCreatorAI.py index 51791ee04a..e2b1298590 100755 --- a/direct/src/level/EntityCreatorAI.py +++ b/direct/src/level/EntityCreatorAI.py @@ -10,6 +10,7 @@ from PythonUtil import Functor # some useful constructor functions # ctor functions for entities must take # (level, entId, zoneId) +# and they must return the entity that was created, or 'nothing' # this func creates distributed entities whose constructors take # (air, level doId, entId) @@ -25,11 +26,12 @@ def createDistributedEntity(AIclass, level, entId, zoneId): def createLocalEntity(AIclass, level, entId, zoneId): """create a local entity""" ent = AIclass(level, entId) + return ent # 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 + return 'nothing' class EntityCreatorAI(EntityCreatorBase.EntityCreatorBase): """This class is responsible for creating instances of Entities on the AI. diff --git a/direct/src/level/EntityCreatorBase.py b/direct/src/level/EntityCreatorBase.py index 1d06e68b80..f98570d725 100755 --- a/direct/src/level/EntityCreatorBase.py +++ b/direct/src/level/EntityCreatorBase.py @@ -20,7 +20,9 @@ class EntityCreatorBase: (entType, entId)) # inheritor must define doCreateEntity - return self.doCreateEntity(self.entType2Ctor[entType], entId) + ent = self.doCreateEntity(self.entType2Ctor[entType], entId) + assert ent is not None # must be Entity or 'nothing' + return ent def getEntityTypes(self): """by definition, this object knows the full list of entity types diff --git a/direct/src/level/EntityTypeDesc.py b/direct/src/level/EntityTypeDesc.py index 5772ee2590..b045cf3bc8 100755 --- a/direct/src/level/EntityTypeDesc.py +++ b/direct/src/level/EntityTypeDesc.py @@ -26,8 +26,14 @@ class EntityTypeDesc: self.attribDescDict[attribName] = desc def isConcrete(self): + """ means that entity of this exact type can be created """ return not self.__class__.__dict__.has_key('abstract') + def isPermanent(self): + """ means that entity of this exact type cannot be inserted or + removed in the editor """ + return self.__class__.__dict__.has_key('permanent') + def getOutputType(self): return self.output diff --git a/direct/src/level/EntityTypeRegistry.py b/direct/src/level/EntityTypeRegistry.py index 24329c04da..19f0ffa891 100755 --- a/direct/src/level/EntityTypeRegistry.py +++ b/direct/src/level/EntityTypeRegistry.py @@ -44,6 +44,14 @@ class EntityTypeRegistry: self.output2typeNames.setdefault(outputType, []) self.output2typeNames[outputType].append(typename) + # create list of permanent entity typenames (entity types that cannot + # be inserted or removed in the editor) + self.permanentTypeNames = [] + for typename, typeDesc in self.entTypeName2typeDesc.items(): + if typeDesc.isPermanent(): + assert typeDesc.isConcrete() + self.permanentTypeNames.append(typename) + # create mapping of entity typename (abstract or concrete) to list # of entity typenames are concrete and are of that type or derive # from that type @@ -80,6 +88,9 @@ class EntityTypeRegistry: def isDerivedAndBase(self, entType, baseEntType): return entType in self.getDerivedTypeNames(baseEntType) + def getPermanentTypeNames(self): + return self.permanentTypeNames + def __hash__(self): return hash(repr(self)) def __repr__(self): diff --git a/direct/src/level/EntityTypes.py b/direct/src/level/EntityTypes.py index 4f0f18ac2b..a9c1540685 100755 --- a/direct/src/level/EntityTypes.py +++ b/direct/src/level/EntityTypes.py @@ -14,6 +14,7 @@ class Entity(EntityTypeDesc): class LevelMgr(Entity): type = 'levelMgr' + permanent = 1 attribs = ( ('cogLevel', 0, 'int', {'min':0, 'max':11}), ('cogTrack', 'c', 'choice', {'choiceSet':['c','s','l','m']}), @@ -22,6 +23,7 @@ class LevelMgr(Entity): class EditMgr(Entity): type = 'editMgr' + permanent = 1 attribs = ( ('requestSave', None), ('requestNewEntity', None), @@ -29,18 +31,6 @@ class EditMgr(Entity): ('removeEntity', None), ) -class LogicGate(Entity): - type = 'logicGate' - output = 'bool' - attribs = ( - ('input1Event', 0, 'entId', {'output':'bool'}), - ('input2Event', 0, 'entId', {'output':'bool'}), - ('isInput1', 0, 'bool'), - ('isInput2', 0, 'bool'), - ('logicType', 'or', 'choice', - {'choiceSet':['or','and','xor','nand','nor','xnor']}), - ) - class Nodepath(Entity): type = 'nodepath' attribs = ( @@ -51,6 +41,7 @@ class Nodepath(Entity): class Zone(Nodepath): type = 'zone' + permanent = 1 delAttribs = ( 'parentEntId', 'pos', @@ -58,10 +49,22 @@ class Zone(Nodepath): ) attribs = ( ('description', '', 'string'), - ('modelZoneNum', None, 'int'), + ('modelZoneNum', -1, 'int'), ('visibility', [], 'visZoneList'), ) +class LogicGate(Entity): + type = 'logicGate' + output = 'bool' + attribs = ( + ('input1Event', 0, 'entId', {'output':'bool'}), + ('input2Event', 0, 'entId', {'output':'bool'}), + ('isInput1', 0, 'bool'), + ('isInput2', 0, 'bool'), + ('logicType', 'or', 'choice', + {'choiceSet':['or','and','xor','nand','nor','xnor']}), + ) + class CutScene(Entity): type = 'cutScene' output = 'bool' diff --git a/direct/src/level/Level.py b/direct/src/level/Level.py index 9752b54043..18e590da75 100755 --- a/direct/src/level/Level.py +++ b/direct/src/level/Level.py @@ -4,6 +4,7 @@ import DirectNotifyGlobal import string import LevelConstants from PythonUtil import lineInfo, uniqueElements +import types """ Any data that can be edited by a level editor must be represented as @@ -62,20 +63,26 @@ class Level: # TODO: maybe we should leave this to a subclass or the level user self.createAllEntities(priorityTypes=['levelMgr','zone']) + # check on the singleton entities + # we make our own references to them rather than expect them to + # create the references so that the editor can create dummy + # do-nothing entities + # there should be one and only one levelMgr assert len(self.entType2ids['levelMgr']) == 1 - self.levelMgrEntity = self.getEntity(self.entType2ids['levelMgr'][0]) - assert self.levelMgrEntity.entId == LevelConstants.LevelMgrEntId + assert self.entType2ids['levelMgr'][0] == LevelConstants.LevelMgrEntId + self.levelMgrEntity = self.getEntity(LevelConstants.LevelMgrEntId) if __debug__: # there should be one and only one editMgr assert len(self.entType2ids['editMgr']) == 1 - self.editMgrEntity = self.getEntity(self.entType2ids['editMgr'][0]) - assert self.editMgrEntity.entId == LevelConstants.EditMgrEntId + assert self.entType2ids['editMgr'][0] == \ + LevelConstants.EditMgrEntId + self.editMgrEntity = self.getEntity(LevelConstants.EditMgrEntId) - # make sure the uberzone is there + # there should be one and only one UberZone assert LevelConstants.UberZoneEntId in self.entType2ids['zone'] - self.UberZoneEntity = self.getEntity(LevelConstants.UberZoneEntId) + self.uberZoneEntity = self.getEntity(LevelConstants.UberZoneEntId) self.initialized = 1 @@ -153,7 +160,7 @@ class Level: # 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: + if entity is not 'nothing': assert uniqueElements(self.createdEntIds) assert entId not in self.createdEntIds self.createdEntIds.append(entId) diff --git a/direct/src/level/LevelMgrBase.py b/direct/src/level/LevelMgrBase.py index 0306591ad2..c810479b57 100755 --- a/direct/src/level/LevelMgrBase.py +++ b/direct/src/level/LevelMgrBase.py @@ -6,9 +6,7 @@ class LevelMgrBase(Entity.Entity): """This class contains LevelMgr code shared by the AI and client""" def __init__(self, level, entId): Entity.Entity.__init__(self, level, entId) - self.level.levelMgr = self def destroy(self): - del self.level.levelMgr Entity.Entity.destroy(self) self.ignoreAll() diff --git a/direct/src/level/LevelSpec.py b/direct/src/level/LevelSpec.py index 4bea391b15..d8850fa678 100755 --- a/direct/src/level/LevelSpec.py +++ b/direct/src/level/LevelSpec.py @@ -4,24 +4,24 @@ import DirectNotifyGlobal from PythonUtil import list2dict, uniqueElements import string import LevelConstants +import types if __debug__: import os -if __debug__: - def makeNewSpec(filename, modelPath, ): - spec = LevelSpec() - spec.doSetAttrib(LevelConstants.LevelMgrEntId, - 'modelFilename', modelPath) - spec.saveToDisk(filename, makeBackup=0) - class LevelSpec: """contains spec data for a level, is responsible for handing the data out upon request, as well as recording changes made during editing, and saving out modified spec data""" notify = DirectNotifyGlobal.directNotify.newCategory("LevelSpec") - def __init__(self, specDict=None, scenario=0): - self.specDict = specDict + def __init__(self, spec=None, scenario=0): + """spec must be passed in as a python module or a dictionary""" + if type(spec) is types.ModuleType: + self.specDict = spec.levelSpec + else: + # we need this for repr/eval-ing LevelSpecs + assert type(spec) is types.DictType + self.specDict = spec if __debug__: newSpec = 0 @@ -179,6 +179,8 @@ class LevelSpec: if self.hasLevel(): # notify the level self.level.handleEntityInsert(entId) + else: + LevelSpec.notify.warning('no level to be notified of insertion') def removeEntity(self, entId): LevelSpec.notify.info('removing entity %s' % entId) @@ -187,6 +189,8 @@ class LevelSpec: if self.hasLevel(): # notify the level self.level.handleEntityRemove(entId) + else: + LevelSpec.notify.warning('no level to be notified of removal') # remove the entity's spec dict = self.entId2specDict[entId] diff --git a/direct/src/level/LogicGateAI.py b/direct/src/level/LogicGateAI.py index 7fbf3870b2..5ee56d07cb 100755 --- a/direct/src/level/LogicGateAI.py +++ b/direct/src/level/LogicGateAI.py @@ -96,12 +96,11 @@ class LogicGateAI(Entity.Entity, PandaObject.PandaObject): def destroy(self): assert(self.debugPrint("destroy()")) - self.ignore(self.input1) - self.input1 = None - self.ignore(self.input2) - self.input2 = None + self.ignore(self.input1Event) + self.input1Event = None + self.ignore(self.input2Event) + self.input2Event = None Entity.Entity.destroy(self) - PandaObject.PandaObject.destroy(self) def setLogicType(self, logicType): assert(self.debugPrint("setLogicType(logicType=%s)"%(logicType,))) @@ -111,15 +110,15 @@ class LogicGateAI(Entity.Entity, PandaObject.PandaObject): def setIsInput1(self, isTrue): assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,))) - if 1 or (not isTrue) != (not self.input1): - # ...the logical state of self.input1 has changed. + if 1 or (not isTrue) != (not self.input1Event): + # ...the logical state of self.input1Event has changed. self.isInput1=isTrue self.logicTest(self, isTrue, self.isInput2) def setIsInput2(self, isTrue): assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,))) - if 1 or (not isTrue) != (not self.input2): - # ...the logical state of self.input2 has changed. + if 1 or (not isTrue) != (not self.input2Event): + # ...the logical state of self.input2Event has changed. self.isInput2=isTrue self.logicTest(self, isTrue, self.isInput1) diff --git a/direct/src/level/SpecUtil.py b/direct/src/level/SpecUtil.py new file mode 100755 index 0000000000..ad0db9be11 --- /dev/null +++ b/direct/src/level/SpecUtil.py @@ -0,0 +1,25 @@ +"""SpecUtil module: contains utility functions for creating and managing level specs""" + +import LevelSpec +import LevelConstants + +def makeNewSpec(filename, modelPath): + """call this to create a new level spec for the level model at 'modelPath'. + Spec will be saved as 'filename'""" + spec = LevelSpec.LevelSpec() + spec.doSetAttrib(LevelConstants.LevelMgrEntId, + 'modelFilename', modelPath) + spec.saveToDisk(filename, makeBackup=0) + +def updateSpec(specModule, modelPath=None): + """Call this to update an existing levelSpec to work with a new level + model. If the level model has a new path, pass it in as 'modelPath'. + specModule must be a Python module""" + spec = LevelSpec.LevelSpec(specModule) + if modelPath is None: + modelPath = spec.getEntitySpec( + LevelConstants.LevelMgrEntId)['modelFilename'] + + # ... + + spec.saveToDisk()