combat.py (14333B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\behaviors\actions\combat.py 2 import logging 3 import random 4 import uthread2 5 from ballpark.const import DESTINY_MODE_MISSILE 6 from ballpark.messenger.const import MESSAGE_ENTITY_SUPER_WEAPON_STOPPED 7 from behaviors import status 8 from behaviors.const.behaviorroles import COMBAT_EFFECTS_BY_ROLES 9 from behaviors.tasks import Task 10 from behaviors.utility.dogmatic import can_activate_effect_on_target, is_my_effect_active_on_target, try_activate_effect 11 from ccpProfile import TimedFunction 12 from dogma.const import attributeExplosiveDamage, attributeKineticDamage, attributeThermalDamage 13 from dogma.const import attributeFighterAbilityKamikazeDamageEM, attributeFighterAbilityKamikazeDamageExp 14 from dogma.const import attributeFighterAbilityKamikazeDamageKin 15 from dogma.const import attributeFighterAbilityKamikazeRange, attributeFighterSquadronSize 16 from dogma.const import attributeFighterAbilityKamikazeSignatureRadius, attributeEmDamage 17 from dogma.const import effectEntitySuperWeapon 18 from dogma.const import effectEntitySuperWeaponLanceAllRaces 19 from eve.server.script.mgt.fighters import GetControlledFighterRegistryForSolarsystem 20 from eveexceptions import UserError 21 logger = logging.getLogger(__name__) 22 MIN_THREAT_VALUE = float('-inf') 23 24 class ActivateSuperWeaponOnTarget(Task): 25 26 @TimedFunction('behaviors::actions::combat::ActivateSuperWeaponOnTarget::OnEnter') 27 def OnEnter(self): 28 selectedTarget = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 29 self.SetStatusToFailed() 30 item = self.context.ballpark.inventory2.GetItem(self.context.myItemId) 31 if selectedTarget: 32 if self.context.dogmaLM.dogmaStaticMgr.TypeHasEffect(item.typeID, effectEntitySuperWeapon): 33 self.SetStatusToSuspended() 34 self.TryActivateEffect(effectEntitySuperWeapon, item.ownerID, selectedTarget) 35 if self.context.dogmaLM.dogmaStaticMgr.TypeHasEffect(item.typeID, effectEntitySuperWeaponLanceAllRaces): 36 self.SetStatusToSuspended() 37 self.TryActivateEffect(effectEntitySuperWeaponLanceAllRaces, item.ownerID, selectedTarget) 38 39 @TimedFunction('behaviors::actions::combat::ActivateSuperWeaponOnTarget::TryActivateEffect') 40 def TryActivateEffect(self, effectID, ownerID, targetID): 41 try: 42 self.SubscribeItem(self.context.myItemId, MESSAGE_ENTITY_SUPER_WEAPON_STOPPED, self.OnSuperWeaponStopped) 43 self.context.dogmaLM.ActivateWithContext(ownerID, self.context.myItemId, effectID, targetid=targetID) 44 logger.debug('SuperWeapon: %s: started super weapon on target %s with effect %s', self.context.myItemId, targetID, effectID) 45 except UserError: 46 logger.warn('SuperWeapon: %s: failed to activate on %s with effect %s', self.context.myItemId, targetID, effectID) 47 except RuntimeError: 48 logger.exception('Entity: %s - unexpected exception when starting effect %s on target %s', self.context.myItemId, effectID, targetID) 49 50 def OnSuperWeaponStopped(self): 51 if not self.IsInvalid(): 52 self.status = status.TaskSuccessStatus 53 self.status.OnUpdated(self) 54 logger.debug('SuperWeapon: %s: super weapon cycle stopped', self.context.myItemId) 55 56 def CleanUp(self): 57 if not self.IsInvalid(): 58 self.UnsubscribeItem(self.context.myItemId, MESSAGE_ENTITY_SUPER_WEAPON_STOPPED, self.OnSuperWeaponStopped) 59 Task.CleanUp(self) 60 61 62 class AddCharacterForPodding(Task): 63 64 @TimedFunction('behaviors::actions::combat::AddCharacterForPodding::OnEnter') 65 def OnEnter(self): 66 characterIdForPodding = self.GetLastBlackboardValue(self.attributes.characterAddress) 67 if characterIdForPodding: 68 self.UpdatePoddingList(characterIdForPodding) 69 self.status = status.TaskSuccessStatus 70 71 def UpdatePoddingList(self, characterIdForPodding): 72 poddingList = self.GetLastBlackboardValue(self.attributes.poddingTargetListAddress) 73 if poddingList is None: 74 poddingList = set() 75 if characterIdForPodding not in poddingList: 76 poddingList.add(characterIdForPodding) 77 self.SendBlackboardValue(self.attributes.poddingTargetListAddress, poddingList) 78 79 80 class KamikazeAttack(Task): 81 explosionDelaySeconds = 1.0 82 explosionScheduled = False 83 84 def _ScheduleExplosion(self, targetId): 85 if not self.explosionScheduled: 86 self.explosionScheduled = True 87 uthread2.call_after_simtime_delay(self._ExplodeSquadron, self.explosionDelaySeconds, targetId) 88 89 def EngageTarget(self, targetId): 90 myBall = self.context.myBall 91 notifyRange = self.context.ballpark.dogmaLM.GetAttributeValue(self.context.myItemId, attributeFighterAbilityKamikazeRange) 92 distBetweenBalls = self.context.ballpark.GetSurfaceDist(myBall.id, targetId) 93 if distBetweenBalls <= 0.0: 94 self._ScheduleExplosion(targetId) 95 if self.context.myBall.mode == DESTINY_MODE_MISSILE: 96 self.SetStatusToRunning() 97 return 98 if distBetweenBalls <= notifyRange: 99 self.DoTargetTracking() 100 self.SetStatusToRunning() 101 return 102 myBall.ApproachObject(targetId, approachRange=-999999.0, cruise=False) 103 self.context.myBall.DoTargetTracking = self.DoTargetTracking 104 self.context.myBall.DoCollision = self.DoCollision 105 self.context.ballpark.SetTargetTracking(self.context.myItemId, targetId, notifyRange) 106 self.SetStatusToRunning() 107 108 @TimedFunction('behaviors::actions::combat::KamikazeAttack::OnEnter') 109 def OnEnter(self): 110 selfItem = self.GetSelfAsItem() 111 if not selfItem: 112 self.SetStatusToFailed() 113 return 114 self.Update() 115 116 def _GetTargetBall(self): 117 targetId = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 118 return self.context.ballpark.GetBallOrNone(targetId) 119 120 def Abort(self): 121 122 def noop(*args, **kwargs): 123 pass 124 125 if self.context.myBall.mode == DESTINY_MODE_MISSILE: 126 self.SendBlackboardValue(self.attributes.shouldAbortAddress, False) 127 targetBall = self._GetTargetBall() 128 if targetBall is None: 129 self.context.ballpark.ExplodeEx(self.context.myBall.id) 130 return 131 self.context.myBall.DoCollision = noop 132 self.context.myBall.DoTargetTracking = noop 133 self.context.ballpark.StopEx(self.context.myItemId) 134 self.SendBlackboardValue(self.attributes.shouldAbortAddress, False) 135 self.SetStatusToSuccess() 136 137 def Update(self): 138 shouldAbort = self.GetLastBlackboardValue(self.attributes.shouldAbortAddress) 139 targetBall = self._GetTargetBall() 140 if targetBall is None or shouldAbort: 141 self.Abort() 142 return 143 self.EngageTarget(targetBall.id) 144 145 def GetSelfAsItem(self): 146 return self.context.ballpark.inventory2.GetItem(self.context.myItemId) 147 148 def GetTargetAsItem(self, targetId): 149 return self.context.ballpark.inventory2.GetItem(targetId) 150 151 def _ApplyDamageToTarget(self, fighterID): 152 targetBall = self._GetTargetBall() 153 if targetBall is None: 154 return 155 GAV = self.context.ballpark.dogmaLM.GetAttributeValue 156 squadronSizeMultiplier = GAV(self.context.myItemId, attributeFighterSquadronSize) 157 cloudSize = GAV(fighterID, attributeFighterAbilityKamikazeSignatureRadius) 158 cloudFactor = self.context.ballpark.dogmaLM.GetCloudFactor(targetBall.id, cloudSize) 159 damageAmount = squadronSizeMultiplier * cloudFactor 160 damageByType = {attributeEmDamage: GAV(fighterID, attributeFighterAbilityKamikazeDamageEM), 161 attributeExplosiveDamage: GAV(fighterID, attributeFighterAbilityKamikazeDamageExp), 162 attributeKineticDamage: GAV(fighterID, attributeFighterAbilityKamikazeDamageKin), 163 attributeThermalDamage: GAV(fighterID, attributeThermalDamage)} 164 dogma = sm.GetService('dogma') 165 dogma.ApplyDamage(None, self.context.myItemId, targetBall.id, damageAmount, indirectDogmaLM=self.context.ballpark.dogmaLM, splash=False, damageShieldOnly=False, sourceID=fighterID, weaponDamages=damageByType) 166 167 def _ExplodeSquadron(self, targetID): 168 fighterID = self.context.myItemId 169 self._ApplyDamageToTarget(fighterID) 170 self.context.ballpark.ExplodeEx(fighterID) 171 self.SetStatusToSuccess() 172 173 def DoCollision(self, otherID, otherX, otherY, otherZ): 174 targetID = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 175 if otherID == targetID: 176 self._ScheduleExplosion(targetID) 177 178 def DoTargetTracking(self, *args): 179 uthread2.start_tasklet(self._DoTargetTracking_Threaded, args) 180 181 def _DoTargetTracking_Threaded(self, *args): 182 targetID = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 183 if targetID is None: 184 return 185 fighterID = self.context.myItemId 186 controllerRegistry = GetControlledFighterRegistryForSolarsystem(self.context.ballpark.solarsystemID) 187 controllerID = controllerRegistry.GetControllerForFighter(self.context.myItemId) 188 if controllerID is None: 189 controllerID = 0 190 self.context.ballpark.MissileFollow(fighterID, targetID, controllerID) 191 self.context.ballpark.SetBallMassive(fighterID, False) 192 193 194 class ActivateCombatEffectsOnTarget(Task): 195 196 @TimedFunction('behaviors::actions::combat::ActivateCombatEffectsOnTarget::OnEnter') 197 def OnEnter(self): 198 self.SetStatusToFailed() 199 selfItem = self.GetSelfAsItem() 200 targetId = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 201 if targetId is None: 202 return 203 targetItem = self.GetTargetAsItem(targetId) 204 if not selfItem or not targetItem: 205 return 206 self._ActivateCombatEffectsOnTarget(targetId, selfItem) 207 208 def _ActivateCombatEffectsOnTarget(self, targetId, selfItem): 209 reactionDelay = self._GetReactionDelay() 210 for effectId in self.attributes.effectIds: 211 if self._is_my_effect_active_on_target(effectId, targetId): 212 self.SetStatusToSuccess() 213 continue 214 if self.CanActivateEffectOnTarget(effectId, selfItem.typeID, targetId): 215 self._TryActiveEffect(effectId, targetId, reactionDelay) 216 self.SetStatusToSuccess() 217 218 def _TryActiveEffect(self, effectId, targetId, reactionDelay): 219 uthread2.start_tasklet(try_activate_effect, self, effectId, self.attributes.repeat, target_id=targetId, delay=reactionDelay) 220 221 def GetSelfAsItem(self): 222 return self.context.ballpark.inventory2.GetItem(self.context.myItemId) 223 224 def GetTargetAsItem(self, targetId): 225 return self.context.ballpark.inventory2.GetItem(targetId) 226 227 @TimedFunction('behaviors::actions::combat::ActivateCombatEffectsOnTarget::CanActivateEffectOnTarget') 228 def CanActivateEffectOnTarget(self, effectId, typeId, targetId): 229 return can_activate_effect_on_target(self, typeId, effectId, targetId) 230 231 def _GetReactionDelay(self): 232 if self.HasAttribute('reactionDelayMilliseconds'): 233 return random.uniform(0.0, float(self.attributes.reactionDelayMilliseconds) / 1000.0) 234 return random.random() 235 236 def _is_my_effect_active_on_target(self, effectId, targetId): 237 return is_my_effect_active_on_target(self, effectId, targetId) 238 239 240 class StopCombatEffectsOnTarget(Task): 241 242 @TimedFunction('behaviors::actions::combat::StopCombatEffectsOnTarget::OnEnter') 243 def OnEnter(self): 244 self.status = status.TaskSuccessStatus 245 item = self.context.ballpark.inventory2.GetItem(self.context.myItemId) 246 for effectId in self.attributes.effectIds: 247 if self.context.dogmaLM.dogmaStaticMgr.TypeHasEffect(item.typeID, effectId): 248 self.TryStopEffect(effectId) 249 250 @TimedFunction('behaviors::actions::combat::StopCombatEffectsOnTarget::TryStopEffect') 251 def TryStopEffect(self, effectId): 252 try: 253 self.context.dogmaLM.StopEffect(effectId, self.context.myItemId, forced=True) 254 logger.debug('CeaseFireOnTarget: %s: stopped attacking with effect %s', self.context.myItemId, effectId) 255 except UserError: 256 pass 257 258 259 class OwnerListFilterAction(Task): 260 261 @TimedFunction('behaviors::actions::combat::OwnerListFilterAction::OnEnter') 262 def OnEnter(self): 263 self.SetStatusToSuccess() 264 itemIdSet = self.GetLastBlackboardValue(self.attributes.itemIdSetAddress) 265 if not itemIdSet: 266 return 267 filteredItemIdSet = self.FilterSet(itemIdSet) 268 if filteredItemIdSet != itemIdSet: 269 self.SendBlackboardValue(self.attributes.itemIdSetAddress, filteredItemIdSet) 270 271 def FilterSet(self, itemIdSet): 272 filteredSet = itemIdSet.copy() 273 for itemId in list(itemIdSet): 274 slimItem = self.context.ballpark.slims.get(itemId) 275 if slimItem and slimItem.ownerID in self.attributes.ownerIdSet: 276 filteredSet.discard(itemId) 277 278 return filteredSet 279 280 281 class GetCombatEffectsForRole(Task): 282 283 @TimedFunction('behaviors::actions::combat::GetCombatEffectsForRole::OnEnter') 284 def OnEnter(self): 285 self.SetStatusToSuccess() 286 if self._has_already_registered_combat_effects(): 287 return 288 role = self.GetLastBlackboardValue(self.attributes.roleAddress) 289 combatEffects = self._GetCombatEffectsOnTypeForRole(role) 290 self.SendBlackboardValue(self.attributes.combatEffectsAddress, combatEffects) 291 292 def _has_already_registered_combat_effects(self): 293 return self.GetLastBlackboardValue(self.attributes.combatEffectsAddress) is not None 294 295 def _GetCombatEffectsOnTypeForRole(self, role): 296 combatEffectsByRole = COMBAT_EFFECTS_BY_ROLES.get(role, []) 297 actualCombatEffectsByRoleOnType = [] 298 for effectId in combatEffectsByRole: 299 if self.context.dogmaLM.dogmaStaticMgr.TypeHasEffect(self.context.mySlimItem.typeID, effectId): 300 actualCombatEffectsByRoleOnType.append(effectId) 301 302 return actualCombatEffectsByRoleOnType