소스 검색

added 'permanent' entity types, fixed editor bugs

Darren Ranalli 22 년 전
부모
커밋
cf7a1d1427

+ 13 - 2
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

+ 1 - 1
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.

+ 4 - 3
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):

+ 2 - 1
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):
     """

+ 3 - 1
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.

+ 3 - 1
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

+ 6 - 0
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
 

+ 11 - 0
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):

+ 16 - 13
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'

+ 14 - 7
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)

+ 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"""
     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()

+ 13 - 9
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]

+ 8 - 9
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)
     

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