Browse Source

*** empty log message ***

Dave Schuyler 22 years ago
parent
commit
0ba45e5eda

+ 0 - 0
direct/src/level/AndLoEntity.py


+ 58 - 0
direct/src/level/AndLoEntityAI.py

@@ -0,0 +1,58 @@
+"""AndLoEntityAI.py: contains the AndLoEntity class"""
+
+
+import PandaObject
+import DirectNotifyGlobal
+import Entity
+
+
+class AndLoEntityAI(Entity.Entity, PandaObject.PandaObject):
+    if __debug__:
+        notify = DirectNotifyGlobal.directNotify.newCategory(
+                'AndLoEntityAI')
+
+    def __init__(self, air, levelDoId, entId, zoneId=None):
+        """entId: """
+        assert(self.debugPrint(
+                "AndLoEntityAI(air=%s, levelDoId=%s, entId=%s, zoneId=%s)"
+                %("the air", levelDoId, entId, zoneId)))
+        self.input1 = None
+        self.input2 = None
+        self.levelDoId = levelDoId
+        level = air.doId2do[self.levelDoId]
+        Entity.Entity.__init__(self, level, entId)
+        self.initializeEntity()
+        self.setInput_input1_bool(self.input_input1_bool)
+        self.setInput_input2_bool(self.input_input2_bool)
+    
+    def setIsInput1(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput1=isTrue
+        if self.isInput2:
+            messenger.send(self.getName(), [isTrue])
+    
+    def setIsInput2(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput2=isTrue
+        if self.isInput1:
+            messenger.send(self.getName(), [isTrue])
+    
+    def setInput_input1_bool(self, event):
+        assert(self.debugPrint("setInput_input1_bool(event=%s)"%(event,)))
+        if self.input1:
+            self.ignore(self.input1)
+        self.input1 = "switch-%s"%(event,)
+        if self.input1:
+            self.accept(self.input1, self.setIsInput1)
+    
+    def setInput_input2_bool(self, event):
+        assert(self.debugPrint("setInput_input2_bool(event=%s)"%(event,)))
+        if self.input2:
+            self.ignore(self.input2)
+        self.input2 = "switch-%s"%(event,)
+        if self.input2:
+            self.accept(self.input2, self.setIsInput2)
+    
+    def getName(self):
+        #return "andLoEntity-%s"%(self.entId,)
+        return "switch-%s"%(self.entId,)

+ 56 - 0
direct/src/level/BasicEntities.py

@@ -0,0 +1,56 @@
+"""BasicEntities module: contains the BasicEntities class"""
+
+import Entity
+import DistributedEntity
+import NodePath
+
+# this is an internal class, do not instantiate.
+class privNodePathImpl(NodePath.NodePath):
+    def __init__(self, name):
+        node = hidden.attachNewNode(name)
+        NodePath.NodePath.__init__(self, node)
+
+    def initializeEntity(self):
+        self.callSetters(('pos','x','y','z',
+                          'hpr','h','p','r',
+                          'scale','sx','sx','sz'))
+
+        if hasattr(self, 'parent'):
+            self.level.requestReparent(self, self.parent)
+        
+    def destroy(self):
+        self.removeNode()
+
+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):
+        Entity.Entity.__init__(self, level, entId)
+        privNodePathImpl.__init__(self, str(self))
+        self.initializeEntity()
+
+    def initializeEntity(self):
+        Entity.Entity.initializeEntity(self)
+        privNodePathImpl.initializeEntity(self)
+
+    def destroy(self):
+        Entity.Entity.destroy(self)
+        privNodePathImpl.initializeEntity(self)
+
+class DistributedNodePathEntity(DistributedEntity.DistributedEntity,
+                                privNodePathImpl):
+    """This is a distributed version of NodePathEntity. It should not
+    be instantiated directly; derive your client-side distEntity from
+    this class instead of DistributedEntity."""
+    def __init__(self, cr):
+        DistributedEntity.DistributedEntity.__init__(self, cr)
+        privNodePathImpl.__init__(self, 'DistributedNodePathEntity')
+        
+    def initializeEntity(self):
+        DistributedEntity.DistributedEntity.initializeEntity(self)
+        privNodePathImpl.initializeEntity(self)
+
+    def destroy(self):
+        DistributedEntity.DistributedEntity.destroy(self)
+        privNodePathImpl.destroy(self)

+ 46 - 0
direct/src/level/DistributedEntity.py

@@ -0,0 +1,46 @@
+import DistributedObject
+import Entity
+import DirectNotifyGlobal
+
+class DistributedEntity(DistributedObject.DistributedObject, Entity.Entity):
+    notify = DirectNotifyGlobal.directNotify.newCategory(
+        'DistributedEntity')
+
+    def __init__(self, cr):
+        DistributedObject.DistributedObject.__init__(self, cr)
+
+    def generateInit(self):
+        DistributedEntity.notify.debug('generateInit')
+        DistributedObject.DistributedObject.generateInit(self)
+        # load stuff
+
+    def generate(self):
+        DistributedEntity.notify.debug('generate')
+        DistributedObject.DistributedObject.generate(self)
+
+    def setLevelDoId(self, levelDoId):
+        DistributedEntity.notify.debug('setLevelDoId: %s' % levelDoId)
+        self.levelDoId = levelDoId
+
+    def setEntId(self, entId):
+        DistributedEntity.notify.debug('setEntId: %s' % entId)
+        self.entId = entId
+
+    def announceGenerate(self):
+        DistributedEntity.notify.debug('announceGenerate')
+
+        # 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)
+
+    def disable(self):
+        DistributedEntity.notify.debug('disable')
+        # stop things
+
+    def delete(self):
+        DistributedEntity.notify.debug('delete')
+        # unload things

+ 35 - 0
direct/src/level/DistributedEntityAI.py

@@ -0,0 +1,35 @@
+import DistributedObjectAI
+import Entity
+import DirectNotifyGlobal
+
+class DistributedEntityAI(DistributedObjectAI.DistributedObjectAI,
+                          Entity.Entity):
+    notify = DirectNotifyGlobal.directNotify.newCategory(
+        'DistributedEntityAI')
+
+    def __init__(self, air, levelDoId, entId):
+        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+        self.levelDoId = levelDoId
+        level = self.air.doId2do[self.levelDoId]
+        Entity.Entity.__init__(self, level, entId)
+        # get our spec data
+        self.initializeEntity()
+
+    def generate(self):
+        self.notify.debug('generate')
+        DistributedObjectAI.DistributedObjectAI.generate(self)
+
+    def destroy(self):
+        self.notify.debug('destroy')
+        Entity.Entity.destroy(self)
+        self.requestDelete()
+
+    def delete(self):
+        self.notify.debug('delete')
+        DistributedObjectAI.DistributedObjectAI.delete(self)
+
+    def getLevelDoId(self):
+        return self.levelDoId
+
+    def getEntId(self):
+        return self.entId

+ 151 - 0
direct/src/level/DistributedInteractiveEntity.py

@@ -0,0 +1,151 @@
+""" DistributedInteractiveEntity module: contains the DistributedInteractiveEntity
+    class, the client side representation of a 'landmark door'."""
+
+from ShowBaseGlobal import *
+from ClockDelta import *
+
+import DirectNotifyGlobal
+import FSM
+import DistributedEntity
+
+class DistributedInteractiveEntity(DistributedEntity.DistributedEntity):
+    """
+    DistributedInteractiveEntity class:  The client side representation of any
+    simple animated prop.
+    """
+
+    if __debug__:
+        notify = DirectNotifyGlobal.directNotify.newCategory('DistributedInteractiveEntity')
+
+    def __init__(self, cr):
+        """constructor for the DistributedInteractiveEntity"""
+        DistributedEntity.DistributedEntity.__init__(self, cr)
+        assert(self.debugPrint("__init()"))
+
+        self.fsm = FSM.FSM('Distribu, levelDoId, entIdtedInteractiveEntity',
+                           [State.State('off',
+                                        self.enterOff,
+                                        self.exitOff,
+                                        ['playing',
+                                        'attract']),
+                            State.State('attract',
+                                        self.enterAttract,
+                                        self.exitAttract,
+                                        ['playing']),
+                            State.State('playing',
+                                        self.enterPlaying,
+                                        self.exitPlaying,
+                                        ['attract'])],
+                           # Initial State
+                           'off',
+                           # Final State
+                           'off',
+                          )
+        self.fsm.enterInitialState()
+        # self.generate will be called automatically.
+        
+    def generate(self):
+        """generate(self)
+        This method is called when the DistributedEntity is reintroduced
+        to the world, either for the first time or from the cache.
+        """
+        assert(self.debugPrint("generate()"))
+        DistributedEntity.DistributedEntity.generate(self)
+    
+    def disable(self):
+        assert(self.debugPrint("disable()"))
+        # Go to the off state when the object is put in the cache
+        self.fsm.request("off")
+        DistributedEntity.DistributedEntity.disable(self)
+        # self.delete() will automatically be called.
+    
+    def delete(self):
+        assert(self.debugPrint("delete()"))
+        del self.fsm
+        DistributedEntity.DistributedEntity.delete(self)
+    
+    def setAvatarInteract(self, avatarId):
+        """
+        required dc field.
+        """
+        assert(self.debugPrint("setAvatarInteract(%s)"%(avatarId,)))
+        assert(not self.__dict__.has_key(avatarId))
+        self.avatarId=avatarId
+    
+    def setOwnerDoId(self, ownerDoId):
+        """
+        required dc field.
+        """
+        assert(self.debugPrint("setOwnerDoId(%s)"%(ownerDoId,)))
+        assert(not self.__dict__.has_key("ownerDoId"))
+        self.ownerDoId=ownerDoId
+    
+    def setInitialState(self, state, timestamp):
+        """
+        required dc field.
+        """
+        assert(self.debugPrint("setInitialState(%s, %d)" % (state, timestamp)))
+        assert(not self.__dict__.has_key("initialState"))
+        self.initialState = state
+        self.initialStateTimestamp = timestamp
+        
+    def setState(self, state, timestamp):
+        assert(self.debugPrint("setState(%s, %d)" % (state, timestamp)))
+        self.fsm.request(state, [globalClockDelta.localElapsedTime(timestamp)])
+    
+    #def __getPropNodePath(self):
+    #    assert(self.debugPrint("__getPropNodePath()"))
+    #    if (not self.__dict__.has_key('propNodePath')):
+    #        self.propNodePath=self.cr.playGame.hood.loader.geom.find(
+    #                "**/prop"+self.entID+":*_DNARoot")
+    #    return self.propNodePath
+    
+    def enterTrigger(self, args=None):
+        assert(self.debugPrint("enterTrigger(args="+str(args)+")"))
+        messenger.send("DistributedInteractiveEntity_enterTrigger")
+        self.sendUpdate("requestInteract")
+        # the AI server will reply with toonInteract or rejectInteract.
+    
+    def exitTrigger(self, args=None):
+        assert(self.debugPrint("exitTrigger(args="+str(args)+")"))
+        messenger.send("DistributedInteractiveEntity_exitTrigger")
+        self.sendUpdate("requestExit")
+        # the AI server will reply with avatarExit.
+    
+    def rejectInteract(self):
+        """Server doesn't let the avatar interact with prop"""
+        assert(self.debugPrint("rejectInteract()"))
+        self.cr.playGame.getPlace().setState('walk')
+        
+    def avatarExit(self, avatarId):
+        assert(self.debugPrint("avatarExit(avatarId=%s)"%(avatarId,)))
+    
+    ##### off state #####
+    
+    def enterOff(self):
+        assert(self.debugPrint("enterOff()"))
+    
+    def exitOff(self):
+        assert(self.debugPrint("exitOff()"))
+    
+    ##### attract state #####
+    
+    def enterAttract(self, ts):
+        assert(self.debugPrint("enterAttract()"))
+    
+    def exitAttract(self):
+        assert(self.debugPrint("exitAttract()"))
+    
+    ##### playing state #####
+    
+    def enterPlaying(self, ts):
+        assert(self.debugPrint("enterPlaying()"))
+    
+    def exitPlaying(self):
+        assert(self.debugPrint("exitPlaying()"))
+    
+    if __debug__:
+        def debugPrint(self, message):
+            """for debugging"""
+            return self.notify.debug(
+                    str(self.__dict__.get('entId', '?'))+' '+message)

+ 144 - 0
direct/src/level/DistributedInteractiveEntityAI.py

@@ -0,0 +1,144 @@
+""" DistributedInteractiveEntityAI module: contains the DistributedInteractiveEntityAI
+    class, the server side representation of a simple, animated, interactive 
+    prop."""
+
+
+from AIBaseGlobal import *
+from ClockDelta import *
+
+import DirectNotifyGlobal
+import FSM
+import DistributedEntityAI
+import State
+
+
+class DistributedInteractiveEntityAI(DistributedEntityAI.DistributedEntityAI):
+    """
+    DistributedInteractiveEntityAI class:  The server side representation of
+    an animated prop.  This is the object that remembers what the
+    prop is doing.  The child of this object, the DistributedAnimatedProp
+    object, is the client side version and updates the display that
+    client's display based on the state of the prop.
+    """
+
+    if __debug__:
+        notify = DirectNotifyGlobal.directNotify.newCategory('DistributedInteractiveEntityAI')
+
+    def __init__(self, air, levelDoId, entId):
+        """entId: a unique identifier for this prop."""
+        DistributedEntityAI.DistributedEntityAI.__init__(self, air,
+                                                         levelDoId, entId)
+        assert(self.debugPrint(
+                "DistributedInteractiveEntityAI(air=%s, entId=%s)"
+                %("the air", entId)))
+        self.fsm = FSM.FSM('DistributedInteractiveEntityAI',
+                           [State.State('off',
+                                        self.enterOff,
+                                        self.exitOff,
+                                        ['playing']),
+                            # Attract is an idle mode.  It is named attract
+                            # because the prop is not interacting with an
+                            # avatar, and is therefore trying to attract an
+                            # avatar.
+                            State.State('attract',
+                                        self.enterAttract,
+                                        self.exitAttract,
+                                        ['playing']),
+                            # Playing is for when an avatar is interacting
+                            # with the prop.
+                            State.State('playing',
+                                        self.enterPlaying,
+                                        self.exitPlaying,
+                                        ['attract'])],
+                           # Initial State
+                           'off',
+                           # Final State
+                           'off',
+                          )
+        self.fsm.enterInitialState()
+        self.avatarId=0
+
+
+    def delete(self):
+        del self.fsm
+        DistributedEntityAI.DistributedEntityAI.delete(self)
+    
+    def getAvatarInteract(self):
+        assert(self.debugPrint("getAvatarInteract() returning: %s"%(self.avatarId,)))
+        return self.avatarId
+    
+    def getInitialState(self):
+        assert(self.debugPrint("getInitialState()"))
+        return [self.fsm.getCurrentState().getName(),
+                globalClockDelta.getRealNetworkTime()]
+    
+    def getOwnerDoId(self):
+        assert(self.debugPrint("getOwnerDoId() returning: %s"%(self.ownerDoId,)))
+        return self.ownerDoId
+    
+    def requestInteract(self):
+        assert(self.debugPrint("requestInteract()"))
+        avatarId = self.air.msgSender
+        assert(self.notify.debug("  avatarId:%s"%(avatarId,)))
+        stateName = self.fsm.getCurrentState().getName()
+        if stateName != 'playing':
+            self.sendUpdate("setAvatarInteract", [avatarId])
+            self.avatarId=avatarId
+            self.fsm.request('playing')
+        else:
+            self.sendUpdateToAvatarId(avatarId, "rejectInteract", [])
+    
+    def requestExit(self):
+        assert(self.debugPrint("requestExit()"))
+        avatarId = self.air.msgSender
+        assert(self.notify.debug("  avatarId:%s"%(avatarId,)))
+        if avatarId==self.avatarId:
+            stateName = self.fsm.getCurrentState().getName()
+            if stateName == 'playing':
+                self.sendUpdate("avatarExit", [avatarId])
+                self.fsm.request('attract')
+        else:
+            assert(self.notify.debug("  requestExit: invalid avatarId"))
+    
+    def getState(self):
+        assert(self.debugPrint("getState()"))
+        return [self.fsm.getCurrentState().getName(),
+                globalClockDelta.getRealNetworkTime()]
+    
+    def d_setState(self, state):
+        assert(self.debugPrint("d_setState(state=%s)"%(state,)))
+        self.sendUpdate('setState', [state, globalClockDelta.getRealNetworkTime()])
+    
+    ##### off state #####
+    
+    def enterOff(self):
+        assert(self.debugPrint("enterOff()"))
+        #self.d_setState('off')
+    
+    def exitOff(self):
+        assert(self.debugPrint("exitOff()"))
+    
+    ##### attract state #####
+    
+    def enterAttract(self):
+        assert(self.debugPrint("enterAttract()"))
+        self.d_setState('attract')
+    
+    def exitAttract(self):
+        assert(self.debugPrint("exitAttract()"))
+    
+    ##### open state #####
+    
+    def enterPlaying(self):
+        assert(self.debugPrint("enterPlaying()"))
+        self.d_setState('playing')
+    
+    def exitPlaying(self):
+        assert(self.debugPrint("exitPlaying()"))
+    
+    if __debug__:
+        def debugPrint(self, message):
+            """for debugging"""
+            return self.notify.debug(
+                    str(self.__dict__.get('entId', '?'))+' '+message)
+

+ 254 - 0
direct/src/level/DistributedLevel.py

@@ -0,0 +1,254 @@
+"""DistributedLevel.py: contains the DistributedLevel class"""
+
+from ClockDelta import *
+from PythonUtil import Functor, sameElements, list2dict, uniqueElements
+import ToontownGlobals
+import DistributedObject
+import LevelBase
+import DirectNotifyGlobal
+import EntityCreator
+
+class DistributedLevel(DistributedObject.DistributedObject,
+                       LevelBase.LevelBase):
+    """DistributedLevel"""
+    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevel')
+
+    WantVisibility = config.GetBool('level-visibility', 0)
+    HideZones = config.GetBool('level-hidezones', 1)
+
+    def __init__(self, cr):
+        DistributedObject.DistributedObject.__init__(self, cr)
+        LevelBase.LevelBase.__init__(self)
+
+    def generate(self):
+        self.notify.debug('generate')
+        DistributedObject.DistributedObject.generate(self)
+
+        self.setLevelId(self.doId)
+
+        # this dict stores entity reparents if the parent hasn't been
+        # created yet
+        self.pendingEntId2ParentId = {}
+
+        # Most (if not all) of the timed entities of levels
+        # run on looping intervals that are started once based on
+        # the level's start time.
+        # This sync request is *NOT* guaranteed to finish by the time
+        # the entities get created.
+        # We should listen for any and all time-sync events and re-sync
+        # all our entities at that time.
+        toonbase.tcr.timeManager.synchronize('DistributedLevel.generate')
+
+    # required fields
+    def setZoneIds(self, zoneIds):
+        self.notify.debug('setZoneIds: %s' % zoneIds)
+        self.zoneIds = zoneIds
+
+    def setStartTimestamp(self, timestamp):
+        self.notify.debug('setStartTimestamp: %s' % timestamp)
+        self.startTime = globalClockDelta.networkToLocalTime(timestamp,bits=32)
+
+    def setScenarioIndex(self, scenarioIndex):
+        self.scenarioIndex = scenarioIndex
+
+    def initializeLevel(self, spec):
+        """ subclass should call this as soon as it's located its spec data """
+        LevelBase.LevelBase.initializeLevel(self, spec, self.scenarioIndex)
+        # load stuff
+        self.geom = loader.loadModel(self.spec['modelFilename'])
+
+        def findNumberedNodes(baseString, model=self.geom, self=self):
+            # finds nodes whose name follows the pattern 'baseString#'
+            # where there are no characters after #
+            # returns dictionary that maps # to node
+            potentialNodes = model.findAllMatches(
+                '**/%s*' % baseString).asList()
+            num2node = {}
+            for potentialNode in potentialNodes:
+                name = potentialNode.getName()
+                self.notify.debug('potential match for %s: %s' %
+                                  (baseString, name))
+                try:
+                    num = int(name[len(baseString):])
+                except:
+                    continue
+                
+                num2node[num] = potentialNode
+
+            return num2node
+
+        # find the zones in the model and fix them up
+        self.zoneNum2Node = findNumberedNodes('Zone')
+        # add the UberZone
+        self.zoneNum2Node[0] = self.geom
+
+        # fix up the floor collisions for walkable zones
+        for zoneNum, zoneNode in self.zoneNum2Node.items():
+            # if this is a walkable zone, fix up the model
+            floorColl = zoneNode.find('**/*FloorCollision*')
+            if not floorColl.isEmpty():
+                # rename the floor collision node, and make sure no other
+                # nodes under the ZoneNode have that name
+                floorCollName = 'Zone%sFloor' % zoneNum
+                others = zoneNode.findAllMatches(
+                    '**/%s' % floorCollName).asList()
+                for other in others:
+                    other.setName('%s_renamed' % floorCollName)
+                floorColl.setName(floorCollName)
+
+                # listen for zone enter events from floor collisions
+                def handleZoneEnter(collisionEntry,
+                                    self=self, zoneNum=zoneNum):
+                    # eat the collisionEntry
+                    self.enterZone(zoneNum)
+                self.accept('enter%s' % floorCollName, handleZoneEnter)
+
+        self.zoneNums = self.zoneNum2Node.keys()
+        self.zoneNums.sort()
+        self.notify.debug('zones: %s' % self.zoneNums)
+        assert sameElements(self.zoneNums, self.spec['zones'].keys() + [0])
+
+        # find the doorway nodes
+        self.doorwayNum2Node = findNumberedNodes('Doorway')
+
+        self.initVisibility()
+
+        # create client-side Entities
+        # TODO: only create client-side Entities for the
+        # currently-visible zones?
+        self.localEntities = {}
+        for entId, spec in self.entId2Spec.iteritems():
+            entity = EntityCreator.createEntity(spec['type'], self, entId)
+            if entity is not None:
+                self.localEntities[entId] = entity
+
+        # there should not be any pending reparents left
+        assert len(self.pendingEntId2ParentId) == 0
+
+    def announceGenerate(self):
+        self.notify.debug('announceGenerate')
+        DistributedObject.DistributedObject.announceGenerate(self)
+
+    def disable(self):
+        self.notify.debug('disable')
+        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)
+
+    def getDoorwayNode(self, doorwayNum):
+        # 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])
+        else:
+            parentId = parent
+            assert(entity.entId != 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
+                self.pendingEntId2ParentId[entId] = parentId
+                entity.reparentTo(hidden)
+                # do the reparent once the parent is initialized
+                def doReparent(entId=entId, parentId=parentId, self=self):
+                    entity=self.getEntity(entId)
+                    parent=self.getEntity(parentId)
+                    self.notify.debug(
+                        'performing pending reparent of %s to %s' %
+                        (entity, parent))
+                    entity.reparentTo(parent)
+                    del self.pendingEntId2ParentId[entId]
+                self.accept(self.getEntityCreateEvent(parentId), doReparent)
+
+    def showZone(self, zoneNum):
+        self.zoneNum2Node[zoneNum].show()
+
+    def hideZone(self, zoneNum):
+        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.setAlphaScale(alpha)
+
+    def initVisibility(self):
+        # start out with every zone visible, since none of the zones have
+        # been hidden
+        self.curVisibleZoneNums = list2dict(self.zoneNums)
+        # we have not entered any zone yet
+        self.curZoneNum = None
+
+        # TODO: make this data-driven
+        firstZone = 16
+        self.enterZone(firstZone)
+
+    def enterZone(self, zoneNum):
+        if not DistributedLevel.WantVisibility:
+            return
+
+        if zoneNum == self.curZoneNum:
+            return
+        
+        print "enterZone %s" % zoneNum
+        zoneSpec = self.spec['zones'][zoneNum]
+        # use dicts to efficiently ensure that there are no duplicates
+        visibleZoneNums = list2dict([zoneNum])
+        visibleZoneNums.update(list2dict(zoneSpec['visibility']))
+
+        if DistributedLevel.HideZones:
+            # figure out which zones are new and which are going invisible
+            # use dicts because it's faster to use dict.has_key(x)
+            # than 'x in list'
+            addedZoneNums = []
+            removedZoneNums = []
+            allVZ = dict(visibleZoneNums)
+            allVZ.update(self.curVisibleZoneNums)
+            for vz,None in allVZ.items():
+                new = vz in visibleZoneNums
+                old = vz in self.curVisibleZoneNums
+                if new and old:
+                    continue
+                if new:
+                    addedZoneNums.append(vz)
+                else:
+                    removedZoneNums.append(vz)
+            # show the new, hide the old
+            self.notify.debug('showing zones %s' % addedZoneNums)
+            for az in addedZoneNums:
+                self.showZone(az)
+            self.notify.debug('hiding zones %s' % removedZoneNums)
+            for rz in removedZoneNums:
+                self.hideZone(rz)
+
+        # convert the zone numbers into their actual zoneIds
+        # always include Toontown and factory uberZones
+        visibleZoneIds = [ToontownGlobals.UberZone, self.getZoneId(0)]
+        for vz in visibleZoneNums.keys():
+            visibleZoneIds.append(self.getZoneId(vz))
+        assert(uniqueElements(visibleZoneIds))
+        self.notify.debug('new viz list: %s' % visibleZoneIds)
+
+        toonbase.tcr.sendSetZoneMsg(self.getZoneId(zoneNum), visibleZoneIds)
+        self.curZoneNum = zoneNum
+        self.curVisibleZoneNums = visibleZoneNums

+ 80 - 0
direct/src/level/DistributedLevelAI.py

@@ -0,0 +1,80 @@
+"""DistributedLevelAI.py: contains the DistributedLevelAI class"""
+
+from ClockDelta import *
+import DistributedObjectAI
+import LevelBase
+import DirectNotifyGlobal
+import EntityCreatorAI
+import WeightedChoice
+
+class DistributedLevelAI(DistributedObjectAI.DistributedObjectAI,
+                         LevelBase.LevelBase):
+    """DistributedLevelAI"""
+    notify = DirectNotifyGlobal.directNotify.newCategory('DistributedLevelAI')
+
+    def __init__(self, air):
+        DistributedObjectAI.DistributedObjectAI.__init__(self, air)
+        LevelBase.LevelBase.__init__(self)
+
+    def initializeLevel(self, spec, uberZoneId):
+        self.uberZoneId = uberZoneId
+
+        # choose a scenario
+        wc = WeightedChoice.WeightedChoice(spec['scenarios'], 1)
+        scenario = wc.choose()
+        scenarioIndex = spec['scenarios'].index(scenario)
+
+        LevelBase.LevelBase.initializeLevel(self, spec, scenarioIndex)
+
+        # 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)
+
+    # required-field getters
+    def getZoneIds(self):
+        return self.zoneIds
+
+    def getStartTimestamp(self):
+        return self.startTimestamp
+
+    def getScenarioIndex(self):
+        return self.scenarioIndex
+
+    def generate(self):
+        self.notify.debug('generate')
+        DistributedObjectAI.DistributedObjectAI.generate(self)
+
+        self.setLevelId(self.doId)
+
+        # create the Entities
+        self.aiEntities = {}
+        for entId, spec in self.entId2Spec.iteritems():
+            self.notify.debug('creating %s %s' % (spec['type'], entId))
+            entity = EntityCreatorAI.createEntity(
+                spec['type'], self.air, self.doId, entId,
+                self.getZoneId(spec['zone']))
+            if entity is not None:
+                self.aiEntities[entId] = entity
+
+    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)
+
+        self.destroyLevel()
+
+        DistributedObjectAI.DistributedObjectAI.delete(self)

+ 74 - 0
direct/src/level/Entity.py

@@ -0,0 +1,74 @@
+"""Entity.py: contains the Entity class"""
+
+import string
+
+class Entity:
+    """Entity is the base class for all objects that exist in a Level
+    and can be edited with the LevelEditor."""
+
+    # these are values that can be changed in the level editor
+    # TODO: pick a good name for these values;
+    # parameters, tweakables, attributes, attribs, traits, 
+    Tweakables = (
+        # Name, PythonType, CallSetterOnInitialization
+        ('name', str, 0),
+        ('comment', str, 0),
+        )
+
+    def __init__(self, level, entId, tweakables=None):
+        self.level = level
+        self.entId = entId
+
+        self.tweakables = Entity.Tweakables
+        # add any additional tweakable values
+        if tweakables is not None:
+            self.tweakables.update(tweakables)
+
+    # TODO: funcs to populate the entity with its spec data, and system
+    # to call back when data changes
+    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
+        
+    def privGetSetter(self, attrib):
+        setFuncName = 'set%s%s' % (string.upper(attrib[0]), attrib[1:])
+        if hasattr(self, setFuncName):
+            return getattr(self, setFuncName)
+        return None
+
+    def callSetters(self, attribList):
+        """call this with a list of attribs, and any that exist on the
+        entity and have setters will be passed to their setter"""
+        for attrib in attribList:
+            if hasattr(self, attrib):
+                setter = self.privGetSetter(attrib)
+                if setter is not None:
+                    setter(getattr(self, attrib))
+
+    def paramChanged(self):
+        """This is called when a parameter is tweaked and no setter
+        is called; i.e. the value is set directly on the object.
+        Some Entities might want to completely reset every time anything
+        is tweaked; this is the place to do it; override this func in your
+        derived class
+        """
+        pass
+
+    def getTweakables(self):
+        return self.tweakables
+
+    def privTweak(self, name, value):
+        self.__dict__[name] = value
+
+    def __str__(self):
+        return 'ent%s(%s)' % (self.entId, self.level.getEntityType(self.entId))
+    
+    if __debug__:
+        def debugPrint(self, message):
+            """for debugging"""
+            return self.notify.debug(
+                    str(self.__dict__.get('entId', '?'))+' '+message)

+ 35 - 0
direct/src/level/EntityCreator.py

@@ -0,0 +1,35 @@
+"""EntityCreator.py: contains methods for creation of Entities"""
+
+from PythonUtil import Functor
+import PlatformEntity
+import BasicEntities
+
+def nothing(level, entId):
+    """For entities that don't need to be created by the client"""
+    return None
+
+# Client-side entity ctors:
+EntityType2Ctor = {
+    # Map entity type name to constructor function that takes
+    # (level, entId)
+    'beanBarrel': nothing,
+    'door': nothing,
+    'gagBarrel': nothing,
+    'lift': nothing,
+    'nodepath': BasicEntities.NodePathEntity,
+    #'platform': PlatformEntity.PlatformEntity,
+    'stomper': nothing,
+    'switch': nothing,
+
+    'andLogicObject': nothing,
+    'orLogicObject': nothing,
+    'xorLogicObject': nothing, #XorLoEntity.XorLoEntity,
+    }
+
+# TODO: what other args will be required?
+def createEntity(entType, level, entId):
+    if not EntityType2Ctor.has_key(entType):
+        print "createEntity(entType=%s, entId=%s) not found"%(
+                entType, entId)
+        return None
+    return EntityType2Ctor[entType](level, entId)

+ 49 - 0
direct/src/level/EntityCreatorAI.py

@@ -0,0 +1,49 @@
+"""EntityCreatorAI.py: contains methods for creation of Entities"""
+
+from PythonUtil import Functor
+import DistributedBeanBarrelAI
+import DistributedLiftAI
+import DistributedDoorEntityAI
+import DistributedGagBarrelAI
+#import DistributedStomperPairAI
+import DistributedSwitchAI
+import DistributedStomperAI
+import AndLoEntityAI
+import OrLoEntityAI
+#import XorLoEntity
+
+
+def cDE(AIclass, air, levelDoId, entId, zoneId):
+    """create a distributed entity"""
+    ent = AIclass(air, levelDoId, entId)
+    ent.generateWithRequired(zoneId)
+    return ent
+
+def nothing(air, levelDoId, entId, zoneId):
+    """Create entity that doesn't have a server side representation."""
+    return None
+
+# Server (AI) side factory functions:
+EntityType2Ctor = {
+    # Map entity type name to constructor function that takes
+    # (air, level doId, entId, zoneId)
+    'beanBarrel': Functor(cDE, DistributedBeanBarrelAI.DistributedBeanBarrelAI),
+    'door': DistributedDoorEntityAI.DistributedDoorEntityAI,
+    'gagBarrel': Functor(cDE, DistributedGagBarrelAI.DistributedGagBarrelAI),
+    'lift': Functor(cDE, DistributedLiftAI.DistributedLiftAI),
+    'nodepath': nothing,
+    'platform': nothing,
+    'stomper': Functor(cDE, DistributedStomperAI.DistributedStomperAI),
+    'switch': DistributedSwitchAI.DistributedSwitchAI,
+
+    'andLogicObject': AndLoEntityAI.AndLoEntityAI,
+    'orLogicObject': OrLoEntityAI.OrLoEntityAI,
+    'xorLogicObject': nothing, #XorLoEntity.XorLoEntity,
+    }
+
+def createEntity(entType, air, levelDoId, entId, zoneId):
+    if not EntityType2Ctor.has_key(entType):
+        print "createEntity(entType=%s, air=%s, levelDoId=%s, entId=%s, zoneId=%s) not found"%(
+                entType, "the air", levelDoId, entId, zoneId)
+        return None
+    return EntityType2Ctor[entType](air, levelDoId, entId, zoneId)

+ 91 - 0
direct/src/level/LevelBase.py

@@ -0,0 +1,91 @@
+"""LevelBase.py: contains the LevelBase class"""
+
+import DirectNotifyGlobal
+import string
+
+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)
+
+    def setLevelId(self, levelId):
+        self.levelId = levelId
+
+    def initializeLevel(self, spec, scenarioIndex):
+        """ subclass should call this as soon as it has located
+        its spec data """
+        self.spec = spec
+        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
+
+        # this will be filled in as the entities are created and report in
+        self.entities = {}
+
+    def destroyLevel(self):
+        del self.entities
+        del self.entId2Spec
+        del self.spec
+
+    def initializeEntity(self, entity):
+        """populate an entity with its spec data"""
+        entId = entity.entId
+        spec = self.entId2Spec[entId]
+        # on initialization, set items directly on entity
+        for key,value in spec.items():
+            if key in ('type', 'name', 'comment',):
+                continue
+            if hasattr(entity, key):
+                self.notify.warning('entity %s (%s) already has member %s' %
+                                    (entId, spec['type'], key))
+            entity.__dict__[key] = value
+
+        # entity is initialized, add it to the list of entities
+        self.entities[entity.entId] = entity
+        # send the create event
+        messenger.send(self.getEntityCreateEvent(entity.entId))
+
+        """
+        # set items directly on entity, or call callback functions...
+        for key,value in spec.items():
+            # filter out some entries; we might want to restructure the
+            # spec data into different categories of properties instead
+            # of filtering them here
+            if key in ('type', 'name', 'comment', 'zone',):
+                continue
+            setFuncName = 'set%s%s' % (string.upper(key[0]), key[1:])
+            if hasattr(entity, setFuncName):
+                # call the setter
+                func = getattr(entity, setFuncName)
+                func(value)
+            else:
+                # set the param directly on the object
+                entity.__dict__[key] = value
+                """
+
+    def getEntityCreateEvent(self, entId):
+        """This is the event that is thrown immediately after an entity
+        is initialized"""
+        return 'entityCreate-%s-%s' % (self.levelId, entId)
+
+    def getEntity(self, entId):
+        return self.entities[entId]
+
+    def getEntityType(self, entId):
+        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]

+ 56 - 0
direct/src/level/NandLoEntityAI.py

@@ -0,0 +1,56 @@
+"""NandLoEntityAI.py: contains the NandLoEntity class"""
+
+
+import PandaObject
+import DirectNotifyGlobal
+import Entity
+
+
+class NandLoEntityAI(Entity.Entity, PandaObject.PandaObject):
+    if __debug__:
+        notify = DirectNotifyGlobal.directNotify.newCategory(
+                'NandLoEntityAI')
+
+    def __init__(self, air, levelDoId, entId, zoneId=None):
+        """entId: """
+        assert(self.debugPrint(
+                "NandLoEntityAI(air=%s, levelDoId=%s, entId=%s, zoneId=%s)"
+                %("the air", levelDoId, entId, zoneId)))
+        self.input1 = None
+        self.input2 = None
+        self.levelDoId = levelDoId
+        level = air.doId2do[self.levelDoId]
+        Entity.Entity.__init__(self, level, entId)
+        self.initializeEntity()
+        self.setInput_input1_bool(self.input_input1_bool)
+        self.setInput_input2_bool(self.input_input2_bool)
+    
+    def setIsInput1(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput1=isTrue
+        messenger.send(self.getName(), [not (isTrue or self.isInput2)])
+    
+    def setIsInput2(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput2=isTrue
+        messenger.send(self.getName(), [not (isTrue or self.isInput1)])
+    
+    def setInput_input1_bool(self, event):
+        assert(self.debugPrint("setInput_input1_bool(event=%s)"%(event,)))
+        if self.input1:
+            self.ignore(self.input1)
+        self.input1 = "switch-%s"%(event,)
+        if self.input1:
+            self.accept(self.input1, self.setIsInput1)
+    
+    def setInput_input2_bool(self, event):
+        assert(self.debugPrint("setInput_input2_bool(event=%s)"%(event,)))
+        if self.input2:
+            self.ignore(self.input2)
+        self.input2 = "switch-%s"%(event,)
+        if self.input2:
+            self.accept(self.input2, self.setIsInput2)
+    
+    def getName(self):
+        #return "NandLoEntity-%s"%(self.entId,)
+        return "switch-%s"%(self.entId,)

+ 19 - 0
direct/src/level/OrLoEntity.py

@@ -0,0 +1,19 @@
+"""OrLoEntity.py: contains the OrLoEntity class"""
+
+class OrLoEntity(Entity.Entity):
+    if __debug__:
+        notify = DirectNotifyGlobal.directNotify.newCategory(
+                'OrLoEntity')
+
+    def __init__(self, air, levelDoId, entId, zoneId=None):
+        """entId: """
+        assert(self.debugPrint(
+                "DistributedDoorEntityAI(air=%s, levelDoId=%s, entId=%s, zoneId=%s)"
+                %("the air", levelDoId, entId, zoneId)))
+        self.doLaterTask=None
+        self.isOpenInput = None
+        DistributedInteractiveEntityAI.DistributedInteractiveEntityAI.__init__(
+                self, air, levelDoId, entId)
+        self.fsm.setName('DistributedDoorEntity')
+        if zoneId is not None:
+            self.generateWithRequired(zoneId)

+ 60 - 0
direct/src/level/OrLoEntityAI.py

@@ -0,0 +1,60 @@
+"""OrLoEntityAI.py: contains the OrLoEntity class"""
+
+
+import PandaObject
+import DirectNotifyGlobal
+import Entity
+
+
+class OrLoEntityAI(Entity.Entity, PandaObject.PandaObject):
+    if __debug__:
+        notify = DirectNotifyGlobal.directNotify.newCategory(
+                'OrLoEntityAI')
+
+    def __init__(self, air, levelDoId, entId, zoneId=None):
+        """entId: """
+        assert(self.debugPrint(
+                "OrLoEntityAI(air=%s, levelDoId=%s, entId=%s, zoneId=%s)"
+                %("the air", levelDoId, entId, zoneId)))
+        self.input1 = None
+        self.input2 = None
+        self.levelDoId = levelDoId
+        level = air.doId2do[self.levelDoId]
+        Entity.Entity.__init__(self, level, entId)
+        self.initializeEntity()
+        self.setInput_input1_bool(self.input_input1_bool)
+        self.setInput_input2_bool(self.input_input2_bool)
+    
+    def setIsInput1(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput1=isTrue
+        if not self.isInput2:
+            # ...we already sent the messege when input2 was set.
+            messenger.send(self.getName(), [isTrue])
+    
+    def setIsInput2(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput2=isTrue
+        if not self.isInput1:
+            # ...we already sent the messege when input1 was set.
+            messenger.send(self.getName(), [isTrue])
+    
+    def setInput_input1_bool(self, event):
+        assert(self.debugPrint("setInput_input1_bool(event=%s)"%(event,)))
+        if self.input1:
+            self.ignore(self.input1)
+        self.input1 = "switch-%s"%(event,)
+        if self.input1:
+            self.accept(self.input1, self.setIsInput1)
+    
+    def setInput_input2_bool(self, event):
+        assert(self.debugPrint("setInput_input2_bool(event=%s)"%(event,)))
+        if self.input2:
+            self.ignore(self.input2)
+        self.input2 = "switch-%s"%(event,)
+        if self.input2:
+            self.accept(self.input2, self.setIsInput2)
+    
+    def getName(self):
+        #return "orLoEntity-%s"%(self.entId,)
+        return "switch-%s"%(self.entId,)

+ 3 - 0
direct/src/level/Sources.pp

@@ -0,0 +1,3 @@
+// For now, since we are not installing Python files, this file can
+// remain empty.
+

+ 0 - 0
direct/src/level/XorLoEntity.py


+ 56 - 0
direct/src/level/XorLoEntityAI.py

@@ -0,0 +1,56 @@
+"""XorLoEntityAI.py: contains the XorLoEntity class"""
+
+
+import PandaObject
+import DirectNotifyGlobal
+import Entity
+
+
+class XorLoEntityAI(Entity.Entity, PandaObject.PandaObject):
+    if __debug__:
+        notify = DirectNotifyGlobal.directNotify.newCategory(
+                'XorLoEntityAI')
+
+    def __init__(self, air, levelDoId, entId, zoneId=None):
+        """entId: """
+        assert(self.debugPrint(
+                "XorLoEntityAI(air=%s, levelDoId=%s, entId=%s, zoneId=%s)"
+                %("the air", levelDoId, entId, zoneId)))
+        self.input1 = None
+        self.input2 = None
+        self.levelDoId = levelDoId
+        level = air.doId2do[self.levelDoId]
+        Entity.Entity.__init__(self, level, entId)
+        self.initializeEntity()
+        self.setInput_input1_bool(self.input_input1_bool)
+        self.setInput_input2_bool(self.input_input2_bool)
+    
+    def setIsInput1(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput1=isTrue
+        messenger.send(self.getName(), [(not (isTrue and self.isInput2)) and (isTrue or self.isInput2)])
+    
+    def setIsInput2(self, isTrue):
+        assert(self.debugPrint("setIsInput1(isTrue=%s)"%(isTrue,)))
+        self.isInput2=isTrue
+        messenger.send(self.getName(), [(not (isTrue and self.isInput2)) and (isTrue or self.isInput2)])
+    
+    def setInput_input1_bool(self, event):
+        assert(self.debugPrint("setInput_input1_bool(event=%s)"%(event,)))
+        if self.input1:
+            self.ignore(self.input1)
+        self.input1 = "switch-%s"%(event,)
+        if self.input1:
+            self.accept(self.input1, self.setIsInput1)
+    
+    def setInput_input2_bool(self, event):
+        assert(self.debugPrint("setInput_input2_bool(event=%s)"%(event,)))
+        if self.input2:
+            self.ignore(self.input2)
+        self.input2 = "switch-%s"%(event,)
+        if self.input2:
+            self.accept(self.input2, self.setIsInput2)
+    
+    def getName(self):
+        #return "xorLoEntity-%s"%(self.entId,)
+        return "switch-%s"%(self.entId,)