shipFighterState.py (18700B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\squadrons\shipFighterState.py 2 from collections import namedtuple, defaultdict 3 from eve.client.script.ui.services.menuSvcExtras import movementFunctions 4 from fighters import ABILITY_SLOT_IDS, GetAbilityIDForSlot 5 from fighters.client import ConvertAbilityFailureReason 6 import gametime 7 import signals 8 import logging 9 from eve.common.script.mgt.fighterConst import TUBE_STATE_READY, TUBE_STATE_EMPTY, TUBE_STATE_LANDING, TUBE_STATE_REFUELLING, TUBE_STATE_INSPACE 10 logger = logging.getLogger('ShipFighterState') 11 FighterInSpaceTuple = namedtuple('FighterInSpaceTuple', ['itemID', 12 'typeID', 13 'tubeFlagID', 14 'squadronSize']) 15 FighterInTubeTuple = namedtuple('FighterInTubeTuple', ['itemID', 16 'typeID', 17 'tubeFlagID', 18 'squadronSize']) 19 TubeStatusTuple = namedtuple('TubeStatusTuple', ['statusID', 'startTime', 'endTime']) 20 AbilityActivationStatusTuple = namedtuple('AbilityActivationStatusTuple', ['isPending', 21 'startTime', 22 'durationMs', 23 'isDeactivating']) 24 EMPTY_TUBE = TubeStatusTuple(statusID=TUBE_STATE_EMPTY, startTime=None, endTime=None) 25 26 class ShipFighterState(object): 27 __notifyevents__ = ['OnSessionChanged', 28 'OnFighterTubeContentUpdate', 29 'OnFighterTubeContentEmpty', 30 'OnFighterAddedToController', 31 'OnFighterRemovedFromController', 32 'OnFighterTubeTaskStatus', 33 'OnFighterAbilitySlotActivated', 34 'OnFighterAbilitySlotDeactivated', 35 'OnInSpaceSquadronSizeChanged', 36 'OnEwarStart', 37 'OnEwarEnd'] 38 fightersSvc = None 39 fightersInLaunchTubes = None 40 fightersInSpaceByID = None 41 fightersInSpaceByTube = None 42 statusByTube = None 43 activeAbilities = None 44 abilityChargeCounts = None 45 abilityTargetTracker = None 46 incomingEwarByFighterID = None 47 signalOnFighterTubeStateUpdate = None 48 signalOnFighterTubeContentUpdate = None 49 signalOnFighterInSpaceUpdate = None 50 signalOnAbilityActivationStatusUpdate = None 51 signalOnIncomingEwarStarted = None 52 signalOnIncomingEwarEnded = None 53 signalOnShipLocationChange = None 54 55 def __init__(self, fightersSvc): 56 self.fightersSvc = fightersSvc 57 sm.RegisterNotify(self) 58 self.fightersInLaunchTubes = {} 59 self.fightersInSpaceByID = {} 60 self.fightersInSpaceByTube = {} 61 self.statusByTube = {} 62 self.activeAbilities = {} 63 self.abilityChargeCounts = {} 64 self.abilityCooldowns = {} 65 self.incomingEwarByFighterID = defaultdict(dict) 66 self.abilityTargetTracker = AbilityTargetTracker() 67 if session.shipid: 68 self._UpdateStateForShip() 69 self.signalOnFighterTubeStateUpdate = signals.Signal(signalName='signalOnFighterTubeStateUpdate') 70 self.signalOnFighterTubeContentUpdate = signals.Signal(signalName='signalOnFighterTubeContentUpdate') 71 self.signalOnFighterInSpaceUpdate = signals.Signal(signalName='signalOnFighterInSpaceUpdate') 72 self.signalOnAbilityActivationStatusUpdate = signals.Signal(signalName='signalOnAbilityActivationStatusUpdate') 73 self.signalOnIncomingEwarStarted = signals.Signal(signalName='signalOnIncomingEwarStarted') 74 self.signalOnIncomingEwarEnded = signals.Signal(signalName='signalOnIncomingEwarEnded') 75 self.signalOnShipLocationChange = signals.Signal(signalName='signalOnShipLocationChange') 76 77 def OnSessionChanged(self, isRemote, sess, change): 78 if 'shipid' in change: 79 self._UpdateStateForShip() 80 if 'locationid' in change or 'structureid' in change: 81 for tubeFlagID, tubeStatus in self.statusByTube.iteritems(): 82 if self.GetFightersInTube(tubeFlagID): 83 self._SetTubeStatus(tubeFlagID, TUBE_STATE_READY, None, None) 84 else: 85 self._SetTubeStatus(tubeFlagID, TUBE_STATE_EMPTY, None, None) 86 87 self.signalOnShipLocationChange() 88 self.abilityTargetTracker.FlushAllTargets() 89 90 def OnFighterTubeContentUpdate(self, tubeFlagID, fighterID, fighterTypeID, squadronSize): 91 fighterInTube = FighterInTubeTuple(fighterID, fighterTypeID, tubeFlagID, squadronSize) 92 self.fightersInLaunchTubes[tubeFlagID] = fighterInTube 93 self.signalOnFighterTubeContentUpdate(tubeFlagID) 94 95 def OnFighterTubeContentEmpty(self, tubeFlagID): 96 del self.fightersInLaunchTubes[tubeFlagID] 97 self.signalOnFighterTubeContentUpdate(tubeFlagID) 98 99 def _UpdateAbilitySlotStates(self, fighterID, abilitySlotStates): 100 chargeStates, cooldownStates = abilitySlotStates 101 for abilitySlotID, chargeCount in chargeStates.iteritems(): 102 self.abilityChargeCounts[fighterID, abilitySlotID] = chargeCount 103 104 for abilitySlotID, cooldown in cooldownStates.iteritems(): 105 self.abilityCooldowns[fighterID, abilitySlotID] = cooldown 106 107 def OnFighterAddedToController(self, fighterID, fighterTypeID, tubeFlagID, squadronSize, abilitySlotStates): 108 logger.debug('OnFighterAddedToController %s', (fighterID, 109 fighterTypeID, 110 tubeFlagID, 111 squadronSize, 112 abilitySlotStates)) 113 fighterInSpace = FighterInSpaceTuple(fighterID, fighterTypeID, tubeFlagID, squadronSize) 114 self.fightersInSpaceByID[fighterID] = fighterInSpace 115 self.fightersInSpaceByTube[tubeFlagID] = fighterInSpace 116 self._UpdateAbilitySlotStates(fighterID, abilitySlotStates) 117 self.signalOnFighterInSpaceUpdate(fighterID, tubeFlagID) 118 119 def OnFighterRemovedFromController(self, fighterID, tubeFlagID): 120 logger.debug('OnFighterRemovedFromController %s', (fighterID, tubeFlagID)) 121 self.fightersInSpaceByID.pop(fighterID) 122 self.fightersInSpaceByTube.pop(tubeFlagID) 123 self.incomingEwarByFighterID.pop(fighterID, None) 124 self._DiscardActivatedAbilitiesForFighter(fighterID) 125 for abilitySlotID in ABILITY_SLOT_IDS: 126 self.abilityChargeCounts.pop((fighterID, abilitySlotID), None) 127 self.abilityCooldowns.pop((fighterID, abilitySlotID), None) 128 129 self.abilityTargetTracker.RemoveFighter(fighterID) 130 self.signalOnFighterInSpaceUpdate(fighterID, tubeFlagID) 131 if self.GetTubeStatus(tubeFlagID).statusID not in (TUBE_STATE_LANDING, TUBE_STATE_REFUELLING): 132 self._SetTubeStatus(tubeFlagID, TUBE_STATE_EMPTY, None, None) 133 self.signalOnFighterTubeStateUpdate(tubeFlagID) 134 sm.ScatterEvent('OnFighterRemovedFromControllerClient', fighterID) 135 136 def _DiscardActivatedAbilitiesForFighter(self, fighterID): 137 for abilitySlotID in ABILITY_SLOT_IDS: 138 self.activeAbilities.pop((fighterID, abilitySlotID), None) 139 140 def OnFighterTubeTaskStatus(self, tubeFlagID, statusID, statusStartTime, statusEndTime, userError = None): 141 logger.debug('OnFighterTubeTaskStatus %s', (tubeFlagID, 142 statusID, 143 statusStartTime, 144 statusEndTime, 145 userError)) 146 if userError is not None: 147 sm.GetService('gameui').OnRemoteMessage(*userError.args) 148 self._SetTubeStatus(tubeFlagID, statusID, statusStartTime, statusEndTime) 149 self.signalOnFighterTubeStateUpdate(tubeFlagID) 150 151 def OnAbilityActivationPending(self, fighterID, abilitySlotID): 152 self.activeAbilities[fighterID, abilitySlotID] = AbilityActivationStatusTuple(True, None, None, False) 153 self.signalOnAbilityActivationStatusUpdate(fighterID, abilitySlotID) 154 155 def OnFighterAbilitySlotActivated(self, fighterID, abilitySlotID, remainingChargeCount, startTime, durationMs, cooldown): 156 logger.debug('OnFighterAbilitySlotActivated %s', (fighterID, 157 abilitySlotID, 158 remainingChargeCount, 159 startTime, 160 durationMs, 161 cooldown)) 162 self.activeAbilities[fighterID, abilitySlotID] = AbilityActivationStatusTuple(False, startTime, durationMs, False) 163 if remainingChargeCount is None: 164 self.abilityChargeCounts.pop((fighterID, abilitySlotID), None) 165 else: 166 self.abilityChargeCounts[fighterID, abilitySlotID] = remainingChargeCount 167 if cooldown is None: 168 self.abilityCooldowns.pop((fighterID, abilitySlotID), None) 169 else: 170 self.abilityCooldowns[fighterID, abilitySlotID] = cooldown 171 self.signalOnAbilityActivationStatusUpdate(fighterID, abilitySlotID) 172 173 def OnAbilityDeactivationPending(self, fighterID, abilitySlotID): 174 currentActivationStatus = self.activeAbilities.pop((fighterID, abilitySlotID), None) 175 if currentActivationStatus: 176 newActivationStatus = currentActivationStatus._replace(isDeactivating=True) 177 self.activeAbilities[fighterID, abilitySlotID] = newActivationStatus 178 self.signalOnAbilityActivationStatusUpdate(fighterID, abilitySlotID) 179 180 def OnFighterAbilitySlotDeactivated(self, fighterID, abilitySlotID, failureReason): 181 logger.debug('OnFighterAbilitySlotDeactivated %s', (fighterID, abilitySlotID, failureReason)) 182 self.activeAbilities.pop((fighterID, abilitySlotID), None) 183 self.abilityTargetTracker.RemoveAbilityTarget(fighterID, abilitySlotID) 184 self.signalOnAbilityActivationStatusUpdate(fighterID, abilitySlotID) 185 if failureReason is not None: 186 self._ShowAbilityFailureReason(fighterID, abilitySlotID, failureReason) 187 188 def _ShowAbilityFailureReason(self, fighterID, abilitySlotID, failureReasonUserError): 189 failureReasonUserError = ConvertAbilityFailureReason(fighterID, abilitySlotID, failureReasonUserError) 190 sm.GetService('gameui').OnRemoteMessage(*failureReasonUserError.args) 191 192 def OnInSpaceSquadronSizeChanged(self, fighterID, squadronSize): 193 logger.debug('OnInSpaceSquadronSizeChanged %s', (fighterID, squadronSize)) 194 fighterInSpace = self.fightersInSpaceByID.get(fighterID, None) 195 if fighterInSpace is None: 196 return 197 newFighterInSpace = fighterInSpace._replace(squadronSize=squadronSize) 198 self.fightersInSpaceByID[fighterID] = newFighterInSpace 199 self.fightersInSpaceByTube[fighterInSpace.tubeFlagID] = newFighterInSpace 200 self.signalOnFighterInSpaceUpdate(fighterID, fighterInSpace.tubeFlagID) 201 202 def _UpdateStateForShip(self): 203 self.fightersInLaunchTubes.clear() 204 self.fightersInSpaceByID.clear() 205 self.fightersInSpaceByTube.clear() 206 self.statusByTube.clear() 207 self.activeAbilities.clear() 208 self.abilityChargeCounts.clear() 209 self.abilityCooldowns.clear() 210 self.incomingEwarByFighterID.clear() 211 self.abilityTargetTracker.FlushAllTargets() 212 fightersInTubes, fightersInSpace, abilityStates = self.fightersSvc.GetFightersForShip() 213 for tubeFlagID, fighterItemID, fighterTypeID, squadronSize in fightersInTubes: 214 fighterInTube = FighterInTubeTuple(fighterItemID, fighterTypeID, tubeFlagID, squadronSize) 215 self.fightersInLaunchTubes[tubeFlagID] = fighterInTube 216 self._SetTubeStatus(tubeFlagID, TUBE_STATE_READY, None, None) 217 self.signalOnFighterTubeContentUpdate(tubeFlagID) 218 self.signalOnFighterTubeStateUpdate(tubeFlagID) 219 220 for tubeFlagID, fighterItemID, fighterTypeID, squadronSize in fightersInSpace: 221 fighterInSpace = FighterInSpaceTuple(fighterItemID, fighterTypeID, tubeFlagID, squadronSize) 222 self.fightersInSpaceByID[fighterItemID] = fighterInSpace 223 self.fightersInSpaceByTube[tubeFlagID] = fighterInSpace 224 self._SetTubeStatus(tubeFlagID, TUBE_STATE_INSPACE, None, None) 225 self.signalOnFighterInSpaceUpdate(fighterItemID, tubeFlagID) 226 self.signalOnFighterTubeStateUpdate(tubeFlagID) 227 228 for fighterID, abilitySlotStates in abilityStates.iteritems(): 229 self._UpdateAbilitySlotStates(fighterID, abilitySlotStates) 230 231 def _SetTubeStatus(self, tubeFlagID, statusID, startTime, endTime): 232 self.statusByTube[tubeFlagID] = TubeStatusTuple(statusID=statusID, startTime=startTime, endTime=endTime) 233 234 def GetFightersInTube(self, tubeFlagID): 235 return self.fightersInLaunchTubes.get(tubeFlagID) 236 237 def GetFightersInSpace(self, tubeFlagID): 238 return self.fightersInSpaceByTube.get(tubeFlagID) 239 240 def GetAllFightersIDs(self): 241 return [ f.itemID for f in self.GetAllFighters() ] 242 243 def GetAllFighters(self): 244 return self.fightersInLaunchTubes.values() + self.fightersInSpaceByID.values() 245 246 def GetFightersForTubeID(self, tubeFlagID): 247 return self.GetFightersInTube(tubeFlagID) or self.GetFightersInSpace(tubeFlagID) 248 249 def GetFighterInSpaceByID(self, fighterID): 250 return self.fightersInSpaceByID.get(fighterID) 251 252 def GetAllFighterIDsInSpace(self): 253 return self.fightersInSpaceByID.keys() 254 255 def GetTubeStatus(self, tubeFlagID): 256 return self.statusByTube.get(tubeFlagID, EMPTY_TUBE) 257 258 def IsMyFighterInSpace(self, itemID): 259 return itemID in self.GetAllFighterIDsInSpace() 260 261 def IsAllAbilitiesInSlotActiveOrInCooldown(self, abilitySlotID): 262 for fighterID in self.GetSelectedFightersInSpace(): 263 if self.GetAbilityIDForSlot(fighterID, abilitySlotID) is None: 264 continue 265 if not self.IsAbilityActive(fighterID, abilitySlotID) and self.GetAbilityCooldown(fighterID, abilitySlotID) is None: 266 return False 267 268 return True 269 270 def GetAbilityIDForSlot(self, fighterID, abilitySlotID): 271 fighter = self.GetFighterInSpaceByID(fighterID) 272 if fighter: 273 return GetAbilityIDForSlot(fighter.typeID, abilitySlotID) 274 275 def GetSelectedFightersInSpace(self): 276 return movementFunctions.GetFightersSelectedForNavigation() 277 278 def IsAbilityActive(self, fighterID, abilitySlotID): 279 return (fighterID, abilitySlotID) in self.activeAbilities 280 281 def GetAbilityChargeCount(self, fighterID, abilitySlotID): 282 return self.abilityChargeCounts.get((fighterID, abilitySlotID), None) 283 284 def GetAbilityCooldown(self, fighterID, abilitySlotID): 285 cooldown = self.abilityCooldowns.get((fighterID, abilitySlotID), None) 286 if cooldown is not None: 287 startTime, endTime = cooldown 288 if startTime <= gametime.GetSimTime() < endTime: 289 return (startTime, endTime) 290 291 def GetAbilityActivationStatus(self, fighterID, abilitySlotID): 292 return self.activeAbilities.get((fighterID, abilitySlotID), None) 293 294 def OnAbilityActivatedAtTarget(self, fighterID, abilitySlotID, targetID): 295 self.abilityTargetTracker.RegisterAbilityTarget(fighterID, abilitySlotID, targetID) 296 297 def GetAbilityTargetsForFighter(self, fighterID): 298 return self.abilityTargetTracker.GetAbilityTargetsForFighter(fighterID) 299 300 def ConnectFighterTargetUpdatedHandler(self, handler): 301 return self.abilityTargetTracker.signalOnAbilityTargetsUpdate.connect(handler) 302 303 def DisconnectFighterTargetUpdatedHandler(self, handler): 304 return self.abilityTargetTracker.signalOnAbilityTargetsUpdate.disconnect(handler) 305 306 def OnEwarStart(self, sourceBallID, sourceModuleID, targetBallID, jammingType): 307 if targetBallID not in self.fightersInSpaceByID: 308 return 309 logger.debug('ShipFighterState.OnEwarStart %s', (sourceBallID, 310 sourceModuleID, 311 targetBallID, 312 jammingType)) 313 self.incomingEwarByFighterID[targetBallID][sourceModuleID] = (sourceBallID, jammingType) 314 self.signalOnIncomingEwarStarted(targetBallID, sourceBallID, sourceModuleID, jammingType) 315 316 def OnEwarEnd(self, sourceBallID, sourceModuleID, targetBallID, jammingType): 317 fighterEwar = self.incomingEwarByFighterID.get(targetBallID, None) 318 if fighterEwar is None: 319 return 320 ewar = fighterEwar.pop(sourceModuleID, None) 321 if ewar is None: 322 return 323 logger.debug('ShipFighterState.OnEwarEnd %s', (sourceBallID, 324 sourceModuleID, 325 targetBallID, 326 jammingType)) 327 self.signalOnIncomingEwarEnded(targetBallID, sourceBallID, sourceModuleID, jammingType) 328 329 def GetIncomingFighterEwar(self, fighterID): 330 ewarByModuleID = self.incomingEwarByFighterID.get(fighterID, None) 331 if ewarByModuleID is None: 332 return [] 333 return [ (sourceModuleID, sourceBallID, jammingType) for sourceModuleID, (sourceBallID, jammingType) in ewarByModuleID.iteritems() ] 334 335 def KillEffectsFromBall(self, ballID): 336 for eachTargetID, eachAttackerDict in self.incomingEwarByFighterID.iteritems(): 337 effectsForBall = eachAttackerDict.get(ballID, None) 338 if effectsForBall: 339 sourceBallID, jammingType = effectsForBall 340 self.OnEwarEnd(sourceBallID, ballID, eachTargetID, jammingType) 341 342 343 class AbilityTargetTracker(object): 344 targetsByFighterSlot = None 345 signalOnAbilityTargetsUpdate = None 346 347 def __init__(self): 348 self.targetsByFighterSlot = defaultdict(dict) 349 self.signalOnAbilityTargetsUpdate = signals.Signal(signalName='signalOnAbilityTargetsUpdate') 350 351 def RegisterAbilityTarget(self, fighterID, abilitySlotID, targetID): 352 self.targetsByFighterSlot[fighterID][abilitySlotID] = targetID 353 self.signalOnAbilityTargetsUpdate(fighterID, abilitySlotID, targetID) 354 355 def GetAbilityTargetsForFighter(self, fighterID): 356 return set(self.targetsByFighterSlot.get(fighterID, {}).itervalues()) 357 358 def GetFighterAbilitiesForTarget(self, targetID): 359 abilitiesOnTarget = [] 360 for fighterID, fighterAbilities in self.targetsByFighterSlot.iteritems(): 361 for abilitySlotID, abilityTargetID in fighterAbilities.iteritems(): 362 if abilityTargetID == targetID: 363 abilitiesOnTarget.append((fighterID, abilitySlotID)) 364 365 return abilitiesOnTarget 366 367 def RemoveFighter(self, fighterID): 368 fighterAbilities = self.targetsByFighterSlot.pop(fighterID, None) 369 if fighterAbilities: 370 for abilitySlotID, targetID in fighterAbilities.iteritems(): 371 self.signalOnAbilityTargetsUpdate(fighterID, abilitySlotID, targetID) 372 373 def RemoveAbilityTarget(self, fighterID, abilitySlotID): 374 fighterAbilities = self.targetsByFighterSlot.get(fighterID, None) 375 if fighterAbilities: 376 targetID = fighterAbilities.pop(abilitySlotID, None) 377 if targetID: 378 self.signalOnAbilityTargetsUpdate(fighterID, abilitySlotID, targetID) 379 380 def FlushAllTargets(self): 381 for fighterID in self.targetsByFighterSlot.keys(): 382 self.RemoveFighter(fighterID) 383 384 385 def GetShipFighterState(): 386 return sm.GetService('fighters').shipFighterState