Browse Source

added 'permanent' entity types, fixed editor bugs

Darren Ranalli 22 years ago
parent
commit
cf7a1d1427

+ 13 - 2
direct/src/level/DistributedLevel.py

@@ -156,7 +156,12 @@ class DistributedLevel(DistributedObject.DistributedObject,
         assert len(self.parent2ChildIds) == 0
         assert len(self.parent2ChildIds) == 0
         # make sure the zoneNums from the model match the zoneNums from
         # make sure the zoneNums from the model match the zoneNums from
         # the zone entities
         # 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
         # load stuff
         self.initVisibility()
         self.initVisibility()
@@ -177,7 +182,8 @@ class DistributedLevel(DistributedObject.DistributedObject,
     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
         # and extract zone info
         # 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):
         def findNumberedNodes(baseString, model=self.geom, self=self):
             # finds nodes whose name follows the pattern 'baseString#'
             # finds nodes whose name follows the pattern 'baseString#'
@@ -389,6 +395,11 @@ class DistributedLevel(DistributedObject.DistributedObject,
         if zoneNum == self.curZoneNum:
         if zoneNum == self.curZoneNum:
             return
             return
 
 
+        if zoneNum not in self.zoneNum2entId:
+            DistributedLevel.notify.error(
+                'no ZoneEntity for this zone (%s)!!' % zoneNum)
+            return
+
         zoneEntId = self.zoneNum2entId[zoneNum]
         zoneEntId = self.zoneNum2entId[zoneNum]
         zoneSpec = self.levelSpec.getEntitySpec(zoneEntId)
         zoneSpec = self.levelSpec.getEntitySpec(zoneEntId)
         # use dicts to efficiently ensure that there are no duplicates
         # use dicts to efficiently ensure that there are no duplicates

+ 1 - 1
direct/src/level/EditMgrAI.py

@@ -15,7 +15,7 @@ class EditMgrAI(EditMgrBase.EditMgrBase):
             entIdDict = list2dict(entIds)
             entIdDict = list2dict(entIds)
 
 
             # dumb linear search for now
             # 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
             # Note that this uses the ID range associated with the
             # AI's username, not the username of the user who requested
             # AI's username, not the username of the user who requested
             # the new entity.
             # the new entity.

+ 4 - 3
direct/src/level/Entity.py

@@ -14,9 +14,10 @@ class Entity(DirectObject):
         self.initializeEntity(level, entId)
         self.initializeEntity(level, entId)
 
 
     def initializeEntity(self, 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.level = level
         self.entId = entId
         self.entId = entId
         if (self.level is not None) and (self.entId is not None):
         if (self.level is not None) and (self.entId is not None):

+ 2 - 1
direct/src/level/EntityCreator.py

@@ -11,9 +11,10 @@ import ModelEntity
 
 
 # some useful constructor functions
 # some useful constructor functions
 # ctor functions must take (level, entId)
 # ctor functions must take (level, entId)
+# and they must return the entity that was created, or 'nothing'
 def nothing(*args):
 def nothing(*args):
     """For entities that don't need to be created by the client"""
     """For entities that don't need to be created by the client"""
-    return None
+    return 'nothing'
 
 
 class EntityCreator(EntityCreatorBase.EntityCreatorBase):
 class EntityCreator(EntityCreatorBase.EntityCreatorBase):
     """
     """

+ 3 - 1
direct/src/level/EntityCreatorAI.py

@@ -10,6 +10,7 @@ from PythonUtil import Functor
 # some useful constructor functions
 # some useful constructor functions
 # ctor functions for entities must take
 # ctor functions for entities must take
 #  (level, entId, zoneId)
 #  (level, entId, zoneId)
+# and they must return the entity that was created, or 'nothing'
 
 
 # this func creates distributed entities whose constructors take
 # this func creates distributed entities whose constructors take
 #  (air, level doId, entId)
 #  (air, level doId, entId)
@@ -25,11 +26,12 @@ def createDistributedEntity(AIclass, level, entId, zoneId):
 def createLocalEntity(AIclass, level, entId, zoneId):
 def createLocalEntity(AIclass, level, entId, zoneId):
     """create a local entity"""
     """create a local entity"""
     ent = AIclass(level, entId)
     ent = AIclass(level, entId)
+    return ent
 
 
 # take any number of args to support local and distributed entities
 # take any number of args to support local and distributed entities
 def nothing(*args):
 def nothing(*args):
     """Create entity that doesn't have a server side representation."""
     """Create entity that doesn't have a server side representation."""
-    return None
+    return 'nothing'
 
 
 class EntityCreatorAI(EntityCreatorBase.EntityCreatorBase):
 class EntityCreatorAI(EntityCreatorBase.EntityCreatorBase):
     """This class is responsible for creating instances of Entities on the AI.
     """This class is responsible for creating instances of Entities on the AI.

+ 3 - 1
direct/src/level/EntityCreatorBase.py

@@ -20,7 +20,9 @@ class EntityCreatorBase:
                               (entType, entId))
                               (entType, entId))
 
 
         # inheritor must define doCreateEntity
         # 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):
     def getEntityTypes(self):
         """by definition, this object knows the full list of entity types
         """by definition, this object knows the full list of entity types

+ 6 - 0
direct/src/level/EntityTypeDesc.py

@@ -26,8 +26,14 @@ class EntityTypeDesc:
             self.attribDescDict[attribName] = desc
             self.attribDescDict[attribName] = desc
 
 
     def isConcrete(self):
     def isConcrete(self):
+        """ means that entity of this exact type can be created """
         return not self.__class__.__dict__.has_key('abstract')
         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):
     def getOutputType(self):
         return self.output
         return self.output
 
 

+ 11 - 0
direct/src/level/EntityTypeRegistry.py

@@ -44,6 +44,14 @@ class EntityTypeRegistry:
                     self.output2typeNames.setdefault(outputType, [])
                     self.output2typeNames.setdefault(outputType, [])
                     self.output2typeNames[outputType].append(typename)
                     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
         # create mapping of entity typename (abstract or concrete) to list
         # of entity typenames are concrete and are of that type or derive
         # of entity typenames are concrete and are of that type or derive
         # from that type
         # from that type
@@ -80,6 +88,9 @@ class EntityTypeRegistry:
     def isDerivedAndBase(self, entType, baseEntType):
     def isDerivedAndBase(self, entType, baseEntType):
         return entType in self.getDerivedTypeNames(baseEntType)
         return entType in self.getDerivedTypeNames(baseEntType)
 
 
+    def getPermanentTypeNames(self):
+        return self.permanentTypeNames
+
     def __hash__(self):
     def __hash__(self):
         return hash(repr(self))
         return hash(repr(self))
     def __repr__(self):
     def __repr__(self):

+ 16 - 13
direct/src/level/EntityTypes.py

@@ -14,6 +14,7 @@ class Entity(EntityTypeDesc):
 
 
 class LevelMgr(Entity):
 class LevelMgr(Entity):
     type = 'levelMgr'
     type = 'levelMgr'
+    permanent = 1
     attribs = (
     attribs = (
         ('cogLevel', 0, 'int', {'min':0, 'max':11}),
         ('cogLevel', 0, 'int', {'min':0, 'max':11}),
         ('cogTrack', 'c', 'choice', {'choiceSet':['c','s','l','m']}),
         ('cogTrack', 'c', 'choice', {'choiceSet':['c','s','l','m']}),
@@ -22,6 +23,7 @@ class LevelMgr(Entity):
 
 
 class EditMgr(Entity):
 class EditMgr(Entity):
     type = 'editMgr'
     type = 'editMgr'
+    permanent = 1
     attribs = (
     attribs = (
         ('requestSave', None),
         ('requestSave', None),
         ('requestNewEntity', None),
         ('requestNewEntity', None),
@@ -29,18 +31,6 @@ class EditMgr(Entity):
         ('removeEntity', None),
         ('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):
 class Nodepath(Entity):
     type = 'nodepath'
     type = 'nodepath'
     attribs = (
     attribs = (
@@ -51,6 +41,7 @@ class Nodepath(Entity):
 
 
 class Zone(Nodepath):
 class Zone(Nodepath):
     type = 'zone'
     type = 'zone'
+    permanent = 1
     delAttribs = (
     delAttribs = (
         'parentEntId',
         'parentEntId',
         'pos',
         'pos',
@@ -58,10 +49,22 @@ class Zone(Nodepath):
         )
         )
     attribs = (
     attribs = (
         ('description', '', 'string'),
         ('description', '', 'string'),
-        ('modelZoneNum', None, 'int'),
+        ('modelZoneNum', -1, 'int'),
         ('visibility', [], 'visZoneList'),
         ('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):
 class CutScene(Entity):
     type = 'cutScene'
     type = 'cutScene'
     output = 'bool'
     output = 'bool'

+ 14 - 7
direct/src/level/Level.py

@@ -4,6 +4,7 @@ import DirectNotifyGlobal
 import string
 import string
 import LevelConstants
 import LevelConstants
 from PythonUtil import lineInfo, uniqueElements
 from PythonUtil import lineInfo, uniqueElements
+import types
 
 
 """
 """
 Any data that can be edited by a level editor must be represented as
 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
         # TODO: maybe we should leave this to a subclass or the level user
         self.createAllEntities(priorityTypes=['levelMgr','zone'])
         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
         # there should be one and only one levelMgr
         assert len(self.entType2ids['levelMgr']) == 1
         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__:
         if __debug__:
             # there should be one and only one editMgr
             # there should be one and only one editMgr
             assert len(self.entType2ids['editMgr']) == 1
             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']
         assert LevelConstants.UberZoneEntId in self.entType2ids['zone']
-        self.UberZoneEntity = self.getEntity(LevelConstants.UberZoneEntId)
+        self.uberZoneEntity = self.getEntity(LevelConstants.UberZoneEntId)
 
 
         self.initialized = 1
         self.initialized = 1
 
 
@@ -153,7 +160,7 @@ class Level:
         # NOTE: the entity is not considered to really be created until
         # NOTE: the entity is not considered to really be created until
         # it has all of its initial spec data; see 'initializeEntity'
         # it has all of its initial spec data; see 'initializeEntity'
         # below.
         # below.
-        if entity is not None:
+        if entity is not 'nothing':
             assert uniqueElements(self.createdEntIds)
             assert uniqueElements(self.createdEntIds)
             assert entId not in self.createdEntIds
             assert entId not in self.createdEntIds
             self.createdEntIds.append(entId)
             self.createdEntIds.append(entId)

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

@@ -6,9 +6,7 @@ class LevelMgrBase(Entity.Entity):
     """This class contains LevelMgr code shared by the AI and client"""
     """This class contains LevelMgr code shared by the AI and client"""
     def __init__(self, level, entId):
     def __init__(self, level, entId):
         Entity.Entity.__init__(self, level, entId)
         Entity.Entity.__init__(self, level, entId)
-        self.level.levelMgr = self
 
 
     def destroy(self):
     def destroy(self):
-        del self.level.levelMgr
         Entity.Entity.destroy(self)
         Entity.Entity.destroy(self)
         self.ignoreAll()
         self.ignoreAll()

+ 13 - 9
direct/src/level/LevelSpec.py

@@ -4,24 +4,24 @@ import DirectNotifyGlobal
 from PythonUtil import list2dict, uniqueElements
 from PythonUtil import list2dict, uniqueElements
 import string
 import string
 import LevelConstants
 import LevelConstants
+import types
 if __debug__:
 if __debug__:
     import os
     import os
 
 
-if __debug__:
-    def makeNewSpec(filename, modelPath, ):
-        spec = LevelSpec()
-        spec.doSetAttrib(LevelConstants.LevelMgrEntId,
-                         'modelFilename', modelPath)
-        spec.saveToDisk(filename, makeBackup=0)
-
 class LevelSpec:
 class LevelSpec:
     """contains spec data for a level, is responsible for handing the data
     """contains spec data for a level, is responsible for handing the data
     out upon request, as well as recording changes made during editing, and
     out upon request, as well as recording changes made during editing, and
     saving out modified spec data"""
     saving out modified spec data"""
     notify = DirectNotifyGlobal.directNotify.newCategory("LevelSpec")
     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__:
         if __debug__:
             newSpec = 0
             newSpec = 0
@@ -179,6 +179,8 @@ class LevelSpec:
             if self.hasLevel():
             if self.hasLevel():
                 # notify the level
                 # notify the level
                 self.level.handleEntityInsert(entId)
                 self.level.handleEntityInsert(entId)
+            else:
+                LevelSpec.notify.warning('no level to be notified of insertion')
             
             
         def removeEntity(self, entId):
         def removeEntity(self, entId):
             LevelSpec.notify.info('removing entity %s' % entId)
             LevelSpec.notify.info('removing entity %s' % entId)
@@ -187,6 +189,8 @@ class LevelSpec:
             if self.hasLevel():
             if self.hasLevel():
                 # notify the level
                 # notify the level
                 self.level.handleEntityRemove(entId)
                 self.level.handleEntityRemove(entId)
+            else:
+                LevelSpec.notify.warning('no level to be notified of removal')
 
 
             # remove the entity's spec
             # remove the entity's spec
             dict = self.entId2specDict[entId]
             dict = self.entId2specDict[entId]

+ 8 - 9
direct/src/level/LogicGateAI.py

@@ -96,12 +96,11 @@ class LogicGateAI(Entity.Entity, PandaObject.PandaObject):
 
 
     def destroy(self):
     def destroy(self):
         assert(self.debugPrint("destroy()"))
         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)
         Entity.Entity.destroy(self)
-        PandaObject.PandaObject.destroy(self)
     
     
     def setLogicType(self, logicType):
     def setLogicType(self, logicType):
         assert(self.debugPrint("setLogicType(logicType=%s)"%(logicType,)))
         assert(self.debugPrint("setLogicType(logicType=%s)"%(logicType,)))
@@ -111,15 +110,15 @@ class LogicGateAI(Entity.Entity, PandaObject.PandaObject):
     
     
     def setIsInput1(self, isTrue):
     def setIsInput1(self, isTrue):
         assert(self.debugPrint("setIsInput1(isTrue=%s)"%(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.isInput1=isTrue
             self.logicTest(self, isTrue, self.isInput2)
             self.logicTest(self, isTrue, self.isInput2)
     
     
     def setIsInput2(self, isTrue):
     def setIsInput2(self, isTrue):
         assert(self.debugPrint("setIsInput1(isTrue=%s)"%(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.isInput2=isTrue
             self.logicTest(self, isTrue, self.isInput1)
             self.logicTest(self, isTrue, self.isInput1)
     
     

+ 25 - 0
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()