class PGPhysicsWorld: def __init__(self): # a dynamic world, moving objects or objects which react to other objects should be placed here self.dynamic_world = BulletWorld() CollisionGroup.set_collision_rule(self.dynamic_world) self.dynamic_world.setGravity(Vec3(0, 0, -9.81)) # set gravity # a static world which used to query position/overlap .etc. Don't implement doPhysics() in this world self.static_world = BulletWorld() def report_bodies(self): dynamic_bodies = \ self.dynamic_world.getNumRigidBodies() + self.dynamic_world.getNumGhosts() + self.dynamic_world.getNumVehicles() static_bodies = \ self.static_world.getNumRigidBodies() + self.static_world.getNumGhosts() + self.static_world.getNumVehicles() return "dynamic bodies:{}, static_bodies: {}".format( dynamic_bodies, static_bodies) def destroy(self): self.dynamic_world.clearDebugNode() self.dynamic_world.clearContactAddedCallback() self.dynamic_world.clearFilterCallback() self.static_world.clearDebugNode() self.static_world.clearContactAddedCallback() self.static_world.clearFilterCallback() self.dynamic_world = None self.static_world = None def __del__(self): logging.debug("Physics world is destroyed successfully!")
class DistributedBattleZoneAI(DistributedObjectAI, AvatarWatcher): notify = directNotify.newCategory('DistributedBattleZoneAI') battleType = BattleGlobals.BTBattle MapFormatString = "phase_14/etc/{0}/{0}.bsp" def __init__(self, air): DistributedObjectAI.__init__(self, air) AvatarWatcher.__init__(self, air) # Stores the Cogs each avatar kills. # Key (Avatar Id) # Value: # [{GAG_ID : USES}, [DeadCogData...]] self.avatarData = {} self.avId2suitsTargeting = {} # List of avatars that have acknowledged that they've completed the reward panel sequence. self.avReadyToContinue = [] self.bspLoader = None self.navMeshNp = None # List of info_hint_cover entites, which indicate cover locations for AIs. self.coverKDTree = None self.coverHints = [] self.physicsWorld = None self.gameRules = self.makeGameRules() self.readyAvatars = [] self.map = "" # We have to put entities loaded from the BSP level in a separate zone. # This is a workaround for an issue where the player would receive # DistributedEntity generates but there is no BSP level loaded yet. # # The player will enter the battle zone, and then upon # loading the BSP level, add interest into the entity zone. self.entZone = self.air.allocateZone() def d_emitSound(self, soundPath, worldPos, volume=1.0): if isinstance(soundPath, list) or isinstance(soundPath, tuple): if len(soundPath) == 0: return import random soundPath = random.choice(soundPath) self.sendUpdate( 'emitSound', [soundPath, [worldPos[0], worldPos[1], worldPos[2]], volume]) def getEntZone(self): return self.entZone def loadedMap(self): """ Sent by the client when they finish loading the map. """ pass def setMap(self, map): self.map = map if len(map) and self.bspLoader: self.loadBSPLevel(self.MapFormatString.format(map)) def b_setMap(self, map): self.sendUpdate('setMap', [map]) self.setMap(map) def getMap(self): return self.map def handleAvatarsReady(self): pass def readyToStart(self): avId = self.air.getAvatarIdFromSender() if not avId in self.readyAvatars: avatar = self.air.doId2do.get(avId, None) if avatar: self.gameRules.setupPlayerAttackList(avatar) self.readyAvatars.append(avId) if len(self.readyAvatars) == len(self.watchingAvatarIds): self.handleAvatarsReady() def makeGameRules(self): return GameRulesAI(self) def getGameRules(self): return self.gameRules def getCoverHints(self): return self.coverHints def traceLine(self, start, end): if not self.bspLoader.hasActiveLevel(): return True return self.bspLoader.traceLine(start, end) def deadSuit(self, doId): pass def suitHPAtZero(self, doId): pass def generate(self): DistributedObjectAI.generate(self) self.air.battleZones[self.zoneId] = self self.bspLoader = Py_AI_BSPLoader() self.bspLoader.setAi(True) self.bspLoader.setMaterialsFile("phase_14/etc/materials.txt") #self.bspLoader.setTextureContentsFile("phase_14/etc/texturecontents.txt") self.bspLoader.setServerEntityDispatcher(self) AvatarWatcher.zoneId = self.zoneId # Link up networked entities from src.coginvasion.szboss import (DistributedTriggerAI, DistributedFuncDoorAI, DistributedButtonAI, DistributedFuncRotatingAI, LogicCounter, HintsAI, InfoTimer, InfoBgmAI) from src.coginvasion.szboss.InfoPlayerStart import InfoPlayerStart self.bspLoader.linkServerEntityToClass( "trigger_once", DistributedTriggerAI.DistributedTriggerOnceAI) self.bspLoader.linkServerEntityToClass( "trigger_multiple", DistributedTriggerAI.DistributedTriggerMultipleAI) self.bspLoader.linkServerEntityToClass( "func_door", DistributedFuncDoorAI.DistributedFuncDoorAI) self.bspLoader.linkServerEntityToClass( "func_button", DistributedButtonAI.DistributedButtonAI) self.bspLoader.linkServerEntityToClass( "func_rotating", DistributedFuncRotatingAI.DistributedFuncRotatingAI) self.bspLoader.linkServerEntityToClass("logic_counter", LogicCounter.LogicCounter) self.bspLoader.linkServerEntityToClass("info_hint_cover", HintsAI.InfoHintCover) self.bspLoader.linkServerEntityToClass("info_timer", InfoTimer.InfoTimer) self.bspLoader.linkServerEntityToClass("info_player_start", InfoPlayerStart) self.bspLoader.linkServerEntityToClass("info_bgm", InfoBgmAI.InfoBgmAI) self.physicsWorld = BulletWorld() self.physicsWorld.setGravity(Vec3(0, 0, -32.1740)) self.bspLoader.setPhysicsWorld(self.physicsWorld) def announceGenerate(self): DistributedObjectAI.announceGenerate(self) taskMgr.add(self.__updateTask, self.uniqueName('battleZoneUpdate')) def __updateTask(self, task): dt = globalClock.getDt() try: #self.physicsWorld.doPhysics(dt, metadata.PHYS_SUBSTEPS, dt / (metadata.PHYS_SUBSTEPS + 1)) self.physicsWorld.doPhysics(dt, 0) except: pass self.update() return task.cont def update(self): pass def getPhysicsWorld(self): return self.physicsWorld def loadBSPLevel(self, lfile): self.bspLoader.read(lfile) self.setupNavMesh(self.bspLoader.getResult()) coverPositions = [] self.coverHints = [] for hint in self.bspLoader.findAllEntities("info_hint_cover"): pos = hint.getCEntity().getOrigin() coverPositions.append([pos[0], pos[1], pos[2]]) self.coverHints.append(hint) if len(coverPositions) > 0: self.coverKDTree = cKDTree(coverPositions) def findClosestCoverPoint(self, currPos, n=1): if not self.coverKDTree: return [] return list( self.coverKDTree.query([currPos[0], currPos[1], currPos[2]], n)[1]) def unloadBSPLevel(self): self.cleanupNavMesh() self.coverKDTree = None self.coverHints = [] if self.bspLoader: detachAndRemoveBulletNodes(self.bspLoader.getResult(), world=self.physicsWorld) self.bspLoader.cleanup() def cleanupNavMesh(self): if self.navMeshNp: self.navMeshNp.removeNode() self.navMeshNp = None def setupNavMesh(self, node): self.cleanupNavMesh() nmMgr = base.nmMgr self.navMeshNp = nmMgr.create_nav_mesh() self.navMeshNp.node().set_owner_node_path(node) self.navMeshNp.node().setup() def planPath(self, startPos, endPos): """Uses recast/detour to find a path from the generated nav mesh from the BSP file.""" if not self.navMeshNp: return [startPos, endPos] result = [] valueList = self.navMeshNp.node().path_find_follow(startPos, endPos) currDir = Vec3(0) for i in xrange(valueList.get_num_values()): if i > 0 and i < valueList.get_num_values() - 1: dir = (valueList.get_value(i - 1) - valueList.get_value(i)).normalized() if dir.almostEqual(currDir, 0.05): continue currDir = dir result.append(valueList.get_value(i)) return result def createServerEntity(self, cls, entnum): """ Called by BSPLoader when it encounters a networked entity that we have to generate. """ dobj = cls(self.air, self) dobj.entnum = entnum dobj.bspLoader = self.bspLoader dobj.zoneId = self.entZone return dobj def getBattleType(self): return self.battleType def toonAvailableForTargeting(self, avId): #return len(self.avId2suitsTargeting.get(avId, [])) < 2 return True def clearTargets(self, suitId): # Remove the current target of this suit. for avId in self.avId2suitsTargeting.keys(): if suitId in self.avId2suitsTargeting[avId]: self.avId2suitsTargeting[avId].remove(suitId) def newTarget(self, suitId, targetId): self.clearTargets(suitId) self.avId2suitsTargeting[targetId].append(suitId) def getSuitsTargetingAvId(self, avId): return self.avId2suitsTargeting.get(avId, []) def getNumSuitsTargeting(self, avId): return len(self.getSuitsTargetingAvId(avId)) def acknowledgeAvatarReady(self): avId = self.air.getAvatarIdFromSender() if avId in self.watchingAvatarIds and avId not in self.avReadyToContinue: self.avReadyToContinue.append(avId) if len(self.avReadyToContinue) == len(self.watchingAvatarIds): self.b_rewardSequenceComplete() def b_rewardSequenceComplete(self): timestamp = globalClockDelta.getFrameNetworkTime() self.rewardSequenceComplete(timestamp) self.d_rewardSequenceComplete(timestamp) def d_rewardSequenceComplete(self, timestamp): self.sendUpdate('rewardSequenceComplete', [timestamp]) def rewardSequenceComplete(self, timestamp): pass def resetPhysics(self): if self.physicsWorld: # We are counting on other types of objects never being added on the server side. numRigids = self.physicsWorld.getNumRigidBodies() numGhosts = self.physicsWorld.getNumGhosts() numConstraints = self.physicsWorld.getNumConstraints() for i in xrange(numRigids - 1, -1, -1): self.physicsWorld.removeRigidBody( self.physicsWorld.getRigidBody(i)) for i in xrange(numGhosts - 1, -1, -1): self.physicsWorld.removeGhost(self.physicsWorld.getGhost(i)) for i in xrange(numConstraints - 1, -1, -1): self.physicsWorld.removeConstraint( self.physicsWorld.getConstraint(i)) def delete(self): taskMgr.remove(self.uniqueName('battleZoneUpdate')) self.ignoreEvents() self.coverHints = None del self.air.battleZones[self.zoneId] self.resetStats() self.unloadBSPLevel() self.bspLoader = None self.resetPhysics() self.physicsWorld = None self.readyAvatars = None self.avId2suitsTargeting = None self.watchingAvatarIds = None self.avatarData = None self.gameRules.cleanup() self.gameRules = None self.map = None self.entZone = None DistributedObjectAI.delete(self) def resetStats(self): self.stopTrackingAll() self.avatarData = {} self.avId2suitsTargeting = {} def addAvatar(self, avId, andUpdateAvatars=0): self.setupAvatarData(avId) self.startTrackingAvatarId(avId) if andUpdateAvatars: self.b_setAvatars(self.watchingAvatarIds) def setupAvatarData(self, avId): self.avatarData.update({avId: [{}, []]}) self.avId2suitsTargeting[avId] = [] def handleAvatarLeave(self, avatar, reason): self.removeAvatar(avatar.doId) if reason in [DIED, ZONE_CHANGE]: self.gameRules.restorePlayerBackpack(avatar) def handleAvatarChangeHealth(self, avatar, newHealth, prevHealth): pass def removeAvatar(self, avId, andUpdateAvatars=1): if self.watchingAvatarIds != None: if avId in self.watchingAvatarIds: self.stopTrackingAvatarId(avId) self.sendUpdate('clearAvatarDebris', [avId]) if avId in self.avReadyToContinue: self.avReadyToContinue.remove(avId) if avId in self.avId2suitsTargeting.keys(): del self.avId2suitsTargeting[avId] if avId in self.avatarData.keys(): self.avatarData.pop(avId) if andUpdateAvatars: self.b_setAvatars(self.watchingAvatarIds) # Send the distributed message and # set the avatars on here. def b_setAvatars(self, avIds): self.d_setAvatars(avIds) self.setAvatars(avIds) # Send the distributed message. def d_setAvatars(self, avIds): self.sendUpdate('setAvatars', [avIds]) # Set the avatar ids array to a list of # avatar ids. def setAvatars(self, avIds): # Let's make sure we're only listening to the avatars # we intend to listen to. for avId in itertools.chain(self.watchingAvatarIds, avIds): if avId in avIds: self.addAvatar(avId) else: self.removeAvatar(avId, andUpdateAvatars=0) self.watchingAvatarIds = avIds # Get the avatar ids. def getAvatars(self): return self.watchingAvatarIds def handleGagUse(self, gagId, user): if not self.gameRules.givesExperience(): return gagUses = {} currentKills = [] uses = 0 if user in self.avatarData.keys(): data = self.avatarData.get(user) gagUses = data[0] currentKills = data[1] if gagId in gagUses.keys(): uses = gagUses[gagId] gagUses.update({gagId: uses + 1}) self.avatarData.update({user: [gagUses, currentKills]}) def handleCogDeath(self, cog, killerId): if not self.gameRules.countsTowardsQuests(): return plan = cog.suitPlan cogData = DeadCogData(plan.name, plan.dept, cog.level, cog.variant, cog.hood) gagUses = {} currentKills = [] if killerId in self.avatarData.keys(): data = self.avatarData.get(killerId) gagUses = data[0] currentKills = data[1] currentKills.append(cogData) # Add the Cog kill into the player's dictionary. self.avatarData.update({killerId: [gagUses, currentKills]}) def setToonData(self, netStrings): pass def d_setToonData(self): data = self.parseToonData() self.sendUpdate('setToonData', [data]) def getToonData(self): return [] def parseToonData(self): blobs = [] for avId, data in self.avatarData.iteritems(): avatar = base.air.doId2do.get(avId, None) deadCogData = data[1] favGagId = 0 # Make it whole cream pie by default favGagUses = 0 gagUnlocked = False if avatar: rpData = RPToonData(avatar) trackIncrements = {} for track in avatar.trackExperience.keys(): trackIncrements[track] = 0 for gagId, uses in data[0].iteritems(): gagName = self.air.attackMgr.getAttackName(gagId) gagData = GagGlobals.gagData.get(gagName) track = gagData['track'] if uses > favGagUses: favGagId = gagId trackGags = GagGlobals.TrackGagNamesByTrackName.get(track) incr = (trackGags.index(gagName) + 1) * uses if track in trackIncrements.keys(): incr = incr + trackIncrements[track] trackIncrements[track] = incr rpData.favoriteGag = self.air.attackMgr.getAttackName(favGagId) for track, exp in avatar.trackExperience.iteritems(): rpDataTrack = rpData.getTrackByName(track) increment = trackIncrements.get(track) maxExp = GagGlobals.getMaxExperienceValue(exp, track) incrMaxExp = GagGlobals.getMaxExperienceValue( exp + increment, track) rpDataTrack.exp = exp rpDataTrack.maxExp = maxExp rpDataTrack.increment = increment avatar.trackExperience[track] = (exp + rpDataTrack.increment) if incrMaxExp != maxExp: # We've unlocked a gag. maxExpIndex = GagGlobals.TrackExperienceAmounts.get( track).index(incrMaxExp) newGagName = GagGlobals.TrackGagNamesByTrackName.get( track)[maxExpIndex] gagId = self.air.attackMgr.getAttackIDByName( newGagName) avatar.backpack.addGag(gagId, 1) gagUnlocked = True avatar.b_setTrackExperience( GagGlobals.trackExperienceToNetString( avatar.trackExperience)) if gagUnlocked: netString = avatar.getBackpackAmmo() avatar.d_setBackpackAmmo(netString) # Let's update quest stuff for this avatar. questManager = avatar.questManager objectiveProgresses = [] for quest in questManager.quests.values(): objectiveProgress = [] for i, objective in enumerate(quest.accessibleObjectives): objectiveProgress.append(objective.progress) if objective.type == DefeatCog: progress = objective.progress for killData in deadCogData: if not ( progress == objective.goal ) and objective.isNeededCogFromDeadCogData( killData): progress += 1 elif (progress == objective.goal): break objectiveProgress[i] = progress elif objective.type == DefeatCogBuilding and self.isCogOffice( ) and objective.isAcceptable(self.hood, self.dept, self.numFloors): objectiveProgress[i] = objectiveProgress[i] + 1 elif objective.type == RecoverItem: for killData in deadCogData: objective.handleProgressFromDeadCogData( killData) objectiveProgresses.append(objectiveProgress) questManager.updateQuestData( objectiveProgresses=objectiveProgresses) blobs.append(rpData.toNetString(avId)) return blobs def isCogOffice(self): return self.battleType == BattleGlobals.BTOffice def battleComplete(self): self.d_setToonData() self.startRewardSeq() def startRewardSeq(self): timestamp = globalClockDelta.getFrameNetworkTime() self.sendUpdate('startRewardSeq', [timestamp])