diff --git a/direct/src/level/AndLoEntity.py b/direct/src/level/AndLoEntity.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/direct/src/level/AndLoEntityAI.py b/direct/src/level/AndLoEntityAI.py new file mode 100755 index 0000000000..df37a050e1 --- /dev/null +++ b/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,) diff --git a/direct/src/level/BasicEntities.py b/direct/src/level/BasicEntities.py new file mode 100755 index 0000000000..a7563427f4 --- /dev/null +++ b/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) diff --git a/direct/src/level/DistributedEntity.py b/direct/src/level/DistributedEntity.py new file mode 100755 index 0000000000..332bea04bf --- /dev/null +++ b/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 diff --git a/direct/src/level/DistributedEntityAI.py b/direct/src/level/DistributedEntityAI.py new file mode 100755 index 0000000000..c9fc105ce9 --- /dev/null +++ b/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 diff --git a/direct/src/level/DistributedInteractiveEntity.py b/direct/src/level/DistributedInteractiveEntity.py new file mode 100644 index 0000000000..96b81bdb5e --- /dev/null +++ b/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) diff --git a/direct/src/level/DistributedInteractiveEntityAI.py b/direct/src/level/DistributedInteractiveEntityAI.py new file mode 100644 index 0000000000..0ef449ccda --- /dev/null +++ b/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) + diff --git a/direct/src/level/DistributedLevel.py b/direct/src/level/DistributedLevel.py new file mode 100755 index 0000000000..c4afe371c4 --- /dev/null +++ b/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 diff --git a/direct/src/level/DistributedLevelAI.py b/direct/src/level/DistributedLevelAI.py new file mode 100755 index 0000000000..4370dbb40d --- /dev/null +++ b/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) diff --git a/direct/src/level/Entity.py b/direct/src/level/Entity.py new file mode 100755 index 0000000000..0df39b2453 --- /dev/null +++ b/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) diff --git a/direct/src/level/EntityCreator.py b/direct/src/level/EntityCreator.py new file mode 100755 index 0000000000..5a4d860f4b --- /dev/null +++ b/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) diff --git a/direct/src/level/EntityCreatorAI.py b/direct/src/level/EntityCreatorAI.py new file mode 100755 index 0000000000..cf5a72e3bc --- /dev/null +++ b/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) diff --git a/direct/src/level/LevelBase.py b/direct/src/level/LevelBase.py new file mode 100755 index 0000000000..a246bcbcba --- /dev/null +++ b/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] diff --git a/direct/src/level/NandLoEntityAI.py b/direct/src/level/NandLoEntityAI.py new file mode 100755 index 0000000000..a9c7494baf --- /dev/null +++ b/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,) diff --git a/direct/src/level/OrLoEntity.py b/direct/src/level/OrLoEntity.py new file mode 100755 index 0000000000..b4c78f94d6 --- /dev/null +++ b/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) diff --git a/direct/src/level/OrLoEntityAI.py b/direct/src/level/OrLoEntityAI.py new file mode 100755 index 0000000000..e996ba7d97 --- /dev/null +++ b/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,) diff --git a/direct/src/level/Sources.pp b/direct/src/level/Sources.pp new file mode 100644 index 0000000000..a03ea8c336 --- /dev/null +++ b/direct/src/level/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/direct/src/level/XorLoEntity.py b/direct/src/level/XorLoEntity.py new file mode 100755 index 0000000000..e69de29bb2 diff --git a/direct/src/level/XorLoEntityAI.py b/direct/src/level/XorLoEntityAI.py new file mode 100755 index 0000000000..dc751813db --- /dev/null +++ b/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,)