Browse Source

added LevelSpec data wrapper

Darren Ranalli 22 years ago
parent
commit
5c113af66a

+ 7 - 7
direct/src/level/DistributedLevel.py

@@ -64,11 +64,11 @@ class DistributedLevel(DistributedObject.DistributedObject,
     def levelAnnounceGenerate(self):
     def levelAnnounceGenerate(self):
         pass
         pass
 
 
-    def initializeLevel(self, spec):
-        """subclass should call this as soon as it's located its spec data.
+    def initializeLevel(self, levelSpec):
+        """subclass should call this as soon as it's located its level spec.
         Must be called after obj has been generated."""
         Must be called after obj has been generated."""
         Level.Level.initializeLevel(self, self.doId,
         Level.Level.initializeLevel(self, self.doId,
-                                    spec, self.scenarioIndex)
+                                    levelSpec, self.scenarioIndex)
 
 
         # all of the entities have been created now.
         # all of the entities have been created now.
         # there should not be any pending reparents left at this point
         # there should not be any pending reparents left at this point
@@ -287,7 +287,7 @@ class DistributedLevel(DistributedObject.DistributedObject,
             return
             return
 
 
         zoneEntId = self.zoneNum2entId[zoneNum]
         zoneEntId = self.zoneNum2entId[zoneNum]
-        zoneSpec = self.entId2spec[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
         visibleZoneNums = list2dict([zoneNum])
         visibleZoneNums = list2dict([zoneNum])
         visibleZoneNums.update(list2dict(zoneSpec['visibility']))
         visibleZoneNums.update(list2dict(zoneSpec['visibility']))
@@ -352,12 +352,12 @@ class DistributedLevel(DistributedObject.DistributedObject,
     if __debug__:
     if __debug__:
         # if someone has edited the level, we'll get the full up-to-date
         # if someone has edited the level, we'll get the full up-to-date
         # spec in this message
         # spec in this message
-        def setSpecOverride(self, specStr):
-            if self.spec is not None:
+        def setLevelSpecOverride(self, specStr):
+            if self.levelSpec is not None:
                 return
                 return
 
 
             try:
             try:
-                self.spec = eval(specStr)
+                self.levelSpec = eval(specStr)
             except Exception, e:
             except Exception, e:
                 print ('Exception in %s(%s):\n\t%s' %
                 print ('Exception in %s(%s):\n\t%s' %
                        (lineInfo()[2], specStr, e))
                        (lineInfo()[2], specStr, e))

+ 13 - 11
direct/src/level/DistributedLevelAI.py

@@ -17,11 +17,11 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
         Level.Level.__init__(self)
         Level.Level.__init__(self)
         self.uberZoneId = zoneId
         self.uberZoneId = zoneId
 
 
-    def generate(self, spec):
+    def generate(self, levelSpec):
         self.notify.debug('generate')
         self.notify.debug('generate')
         DistributedObjectAI.DistributedObjectAI.generate(self)
         DistributedObjectAI.DistributedObjectAI.generate(self)
 
 
-        self.initializeLevel(spec)
+        self.initializeLevel(levelSpec)
 
 
         self.sendUpdate('setZoneIds', [self.zoneIds])
         self.sendUpdate('setZoneIds', [self.zoneIds])
         self.sendUpdate('setStartTimestamp', [self.startTimestamp])
         self.sendUpdate('setStartTimestamp', [self.startTimestamp])
@@ -32,19 +32,21 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
         self.destroyLevel()
         self.destroyLevel()
         DistributedObjectAI.DistributedObjectAI.delete(self)
         DistributedObjectAI.DistributedObjectAI.delete(self)
 
 
-    def initializeLevel(self, spec):
+    def initializeLevel(self, levelSpec):
         # record the level's start time so that we can sync the clients
         # record the level's start time so that we can sync the clients
         self.startTime = globalClock.getRealTime()
         self.startTime = globalClock.getRealTime()
         self.startTimestamp = globalClockDelta.localToNetworkTime(
         self.startTimestamp = globalClockDelta.localToNetworkTime(
             self.startTime, bits=32)
             self.startTime, bits=32)
 
 
         # choose a scenario
         # choose a scenario
-        wc = WeightedChoice.WeightedChoice(spec['scenarios'], 1)
-        scenario = wc.choose()
-        scenarioIndex = spec['scenarios'].index(scenario)
+        # make list of lists: [(weight, scenarioIndex), ...]
+        lol = zip(levelSpec.getScenarioWeights(),
+                  range(levelSpec.getNumScenarios()))
+        wc = WeightedChoice.WeightedChoice(lol)
+        scenarioIndex = wc.choose()[1]
 
 
         Level.Level.initializeLevel(self, self.doId,
         Level.Level.initializeLevel(self, self.doId,
-                                    spec, scenarioIndex)
+                                    levelSpec, scenarioIndex)
 
 
     def createEntityCreator(self):
     def createEntityCreator(self):
         """Create the object that will be used to create Entities.
         """Create the object that will be used to create Entities.
@@ -56,7 +58,7 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
         # this func is called before the entity has been created; look
         # this func is called before the entity has been created; look
         # into the spec data, since we can't yet get a handle on the
         # into the spec data, since we can't yet get a handle on the
         # object itself at this point
         # object itself at this point
-        spec = self.entId2spec[entId]
+        spec = self.levelSpec.getEntitySpec(entId)
         type = spec['type']
         type = spec['type']
         if type == 'zone':
         if type == 'zone':
             if not hasattr(self, 'zoneNum2zoneId'):
             if not hasattr(self, 'zoneNum2zoneId'):
@@ -85,11 +87,11 @@ class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
 
 
             # send a copy of the entire spec for any new users that
             # send a copy of the entire spec for any new users that
             # might come in
             # might come in
-            ##self.sendUpdate('setSpecOverride', [repr(self.spec)])
+            ##self.sendUpdate('setSpecOverride', [repr(self.levelSpec)])
 
 
-        def getCurrentSpec(self):
+        def getCurrentLevelSpec(self):
             """returns the complete, current spec, including any edits"""
             """returns the complete, current spec, including any edits"""
-            return self.spec
+            return self.levelSpec
 
 
         """
         """
         def getSpecOverride(self):
         def getSpecOverride(self):

+ 11 - 22
direct/src/level/Level.py

@@ -34,31 +34,22 @@ class Level:
     UberZoneEntId = 0
     UberZoneEntId = 0
 
 
     def __init__(self):
     def __init__(self):
-        self.spec = None
+        self.levelSpec = None
 
 
-    def initializeLevel(self, levelId, spec, scenarioIndex):
+    def initializeLevel(self, levelId, levelSpec, scenarioIndex):
         """ subclass should call this as soon as it has located
         """ subclass should call this as soon as it has located
         its spec data """
         its spec data """
         self.levelId = levelId
         self.levelId = levelId
-        self.spec = spec
+        self.levelSpec = levelSpec
         self.scenarioIndex = scenarioIndex
         self.scenarioIndex = scenarioIndex
 
 
-        # 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
+        self.levelSpec.setScenario(self.scenarioIndex)
 
 
         # create some handy tables
         # create some handy tables
 
 
         # entity type -> list of entIds
         # 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)
+        self.entType2ids = self.levelSpec.getEntType2ids(
+            self.levelSpec.getAllEntIds())
 
 
         # 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
@@ -83,10 +74,8 @@ class Level:
             del self.createdEntities
             del self.createdEntities
         if hasattr(self, 'entities'):
         if hasattr(self, 'entities'):
             del self.entities
             del self.entities
-        if hasattr(self, 'entId2spec'):
-            del self.entId2spec
-        if hasattr(self, 'spec'):
-            del self.spec
+        if hasattr(self, 'levelSpec'):
+            del self.levelSpec
 
 
     def createEntityCreator(self):
     def createEntityCreator(self):
         self.notify.error(
         self.notify.error(
@@ -131,7 +120,7 @@ class Level:
 
 
     def createEntity(self, entId):
     def createEntity(self, entId):
         assert not self.entities.has_key(entId)
         assert not self.entities.has_key(entId)
-        spec = self.entId2spec[entId]
+        spec = self.levelSpec.getEntitySpec(entId)
         self.notify.debug('creating %s %s' % (spec['type'], entId))
         self.notify.debug('creating %s %s' % (spec['type'], entId))
         entity = self.entityCreator.createEntity(entId)
         entity = self.entityCreator.createEntity(entId)
         # NOTE: the entity is not considered to really be created until
         # NOTE: the entity is not considered to really be created until
@@ -156,7 +145,7 @@ class Level:
         entities; this is called directly by Entity.
         entities; this is called directly by Entity.
         """
         """
         entId = entity.entId
         entId = entity.entId
-        spec = self.entId2spec[entId]
+        spec = self.levelSpec.getEntitySpec(entId)
         # on initialization, set items directly on entity
         # on initialization, set items directly on entity
         for key,value in spec.items():
         for key,value in spec.items():
             if key in ('type', 'name', 'comment',):
             if key in ('type', 'name', 'comment',):
@@ -170,7 +159,7 @@ class Level:
         return self.entities.get(entId)
         return self.entities.get(entId)
 
 
     def getEntityType(self, entId):
     def getEntityType(self, entId):
-        return self.entId2spec[entId]['type']
+        return self.levelSpec.getEntityType(entId)
 
 
     def getZoneId(self, dummy=None, zoneNum=None, entId=None):
     def getZoneId(self, dummy=None, zoneNum=None, entId=None):
         """look up network zoneId by zoneNum or entId"""
         """look up network zoneId by zoneNum or entId"""

+ 203 - 0
direct/src/level/LevelSpec.py

@@ -0,0 +1,203 @@
+"""LevelSpec module: contains the LevelSpec class"""
+
+from PythonUtil import list2dict
+import string
+
+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"""
+    def __init__(self, specDict, scenario=0):
+        self.specDict = specDict
+
+        # this maps an entId to the dict that holds its spec;
+        # entities are either in the global dict or a scenario dict
+        # update the map of entId to spec dict
+        self.entId2specDict = {}
+        self.entId2specDict.update(
+            list2dict(self.getGlobalEntIds(),
+                      value=self.privGetGlobalEntityDict()))
+        for i in range(self.getNumScenarios()):
+            self.entId2specDict.update(
+                list2dict(self.getScenarioEntIds(i),
+                          value=self.privGetScenarioEntityDict(i)))
+
+        self.setScenario(scenario)
+
+    def getNumScenarios(self):
+        return len(self.specDict['scenarios'])
+
+    def getScenarioWeights(self):
+        weights = []
+        for entry in self.specDict['scenarios']:
+            weights.append(entry[1])
+        return weights
+
+    def setScenario(self, scenario):
+        assert scenario in range(0, self.getNumScenarios())
+        self.scenario = scenario
+
+    def getScenario(self):
+        return self.scenario
+
+    def getGlobalEntIds(self):
+        return self.privGetGlobalEntityDict().keys()
+
+    def getScenarioEntIds(self, scenario=None):
+        if scenario is None:
+            scenario = self.scenario
+        return self.privGetScenarioEntityDict(scenario).keys()
+
+    def getAllEntIds(self):
+        return self.getGlobalEntIds() + self.getScenarioEntIds()
+
+    def getEntitySpec(self, entId):
+        assert entId in self.entId2specDict
+        specDict = self.entId2specDict[entId]
+        return specDict[entId]
+
+    def getEntityType(self, entId):
+        return self.getEntitySpec(entId)['type']
+
+    def getEntType2ids(self, entIds):
+        """given list of entIds, return dict of entType 2 entIds"""
+        entType2ids = {}
+        for entId in entIds:
+            type = self.getEntityType(entId)
+            entType2ids.setdefault(type, [])
+            entType2ids[type].append(entId)
+        return entType2ids
+
+    # private support functions to abstract dict structure
+    def privGetGlobalEntityDict(self):
+        return self.specDict['globalEntities']
+
+    def privGetScenarioEntityDict(self, scenario):
+        return self.specDict['scenarios'][scenario][0]
+
+    if __debug__:
+        def setAttribChange(self, entId, attrib, value):
+            pass
+
+        def getSpecImportsModuleName(self):
+            # name of module that should be imported by spec py file
+            return 'SpecImports'
+
+        def getPrettyString(self):
+            """Returns a string that contains the spec data, nicely formatted.
+            This should be used when writing the spec out to file."""
+            import pprint
+            
+            tabWidth = 4
+            tab = ' ' * tabWidth
+            # structure names
+            globalEntitiesName = 'GlobalEntities'
+            scenarioEntitiesName = 'Scenario%s'
+            scenarioWeightName = 'Scenarios'
+            topLevelName = 'levelSpec'
+            def getPrettyEntityDictStr(name, dict, tabs=0):
+                def t(n):
+                    return (tabs+n)*tab
+                def sortList(lst, firstElements=[]):
+                    """sort list; elements in firstElements will be put
+                    first, in the order that they appear in firstElements;
+                    rest of elements will follow, sorted"""
+                    elements = list(lst)
+                    # put elements in order
+                    result = []
+                    for el in firstElements:
+                        if el in elements:
+                            result.append(el)
+                            elements.remove(el)
+                    elements.sort()
+                    result.extend(elements)
+                    return result
+   
+                firstTypes = ('levelMgr', 'zone',)
+                firstAttribs = ('type', 'name', 'comment', 'parent',
+                                'pos', 'x', 'y', 'z',
+                                'hpr', 'h', 'p', 'r',
+                                'scale', 'sx', 'sy', 'sz',
+                                'color',
+                                'model',
+                                )
+                str = t(0)+'%s = {\n' % name
+                # get list of types
+                entIds = dict.keys()
+                entType2ids = self.getEntType2ids(entIds)
+                # put types in order
+                types = sortList(entType2ids.keys(), firstTypes)
+                for type in types:
+                    str += t(1)+'# %s\n' % string.upper(type)
+                    entIds = entType2ids[type]
+                    entIds.sort()
+                    for entId in entIds:
+                        str += t(1)+'%s: {\n' % entId
+                        spec = dict[entId]
+                        attribs = sortList(spec.keys(), firstAttribs)
+                        for attrib in attribs:
+                            str += t(2)+"'%s': %s,\n" % (attrib,
+                                                         repr(spec[attrib]))
+                        str += t(2)+'},\n'
+                        
+                str += t(1)+'}\n'
+                return str
+            def getPrettyScenarioWeightTableStr(tabs=0, self=self):
+                def t(n):
+                    return (tabs+n)*tab
+                str  = t(0)+'%s = [\n' % scenarioWeightName
+                for i in range(self.getNumScenarios()):
+                    str += t(1)+'[%s, %s],\n' % (scenarioEntitiesName % i,
+                                                  self.getScenarioWeights()[i])
+                str += t(1)+']\n'
+                return str
+            def getPrettyTopLevelDictStr(tabs=0):
+                def t(n):
+                    return (tabs+n)*tab
+                str  = t(0)+'%s = {\n' % topLevelName
+                str += t(1)+"'globalEntities': %s,\n" % globalEntitiesName
+                str += t(1)+"'scenarios': %s,\n" % scenarioWeightName
+                str += t(1)+'}\n'
+                return str
+            
+            str  = 'from %s import *\n' % self.getSpecImportsModuleName()
+            str += '\n'
+
+            # add the global entities
+            str += getPrettyEntityDictStr('GlobalEntities',
+                                          self.privGetGlobalEntityDict())
+            str += '\n'
+
+            # add the scenario entities
+            numScenarios = self.getNumScenarios()
+            for i in range(numScenarios):
+                str += getPrettyEntityDictStr('Scenario%s' % i,
+                                              self.privGetScenarioEntityDict(i))
+                str += '\n'
+
+            # add the scenario weight table
+            str += getPrettyScenarioWeightTableStr()
+            str += '\n'
+
+            # add the top-level table
+            str += getPrettyTopLevelDictStr()
+
+            self.testPrettyString(prettyString=str)
+
+            return str
+
+        def testPrettyString(self, prettyString=None):
+            # execute the pretty output in our local scope
+            if prettyString is None:
+                prettyString=self.getPrettyString()
+            exec(prettyString)
+            assert levelSpec == self.specDict, (
+                'LevelSpec pretty string does not match spec data.\n'
+                'pretty=%s\n'
+                'specData=%s' %
+                (levelSpec, self.specDict)
+                )
+
+        def __repr__(self):
+            return 'LevelSpec(%s, scenario=%s)' % (repr(self.specDict),
+                                                   self.scenario)