combattargets.py (39673B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\behaviors\actions\combattargets.py 2 import logging 3 import random 4 from collections import defaultdict 5 import evetypes 6 from ballpark.messenger.const import MESSAGE_TARGET_ADDED 7 from behaviors.blackboards import BlackboardDeletedError 8 from behaviors.const.combat import COMBAT_MAX_EFFECTIVE_FALLOFF_DISTANCE_FACTOR, COMBAT_MAX_EFFECTIVE_DISTANCE_FACTOR 9 from behaviors.utility.ballparks import is_ball_in_park, get_slim_item, is_ball_in_range, is_invulnerable, get_ball_type_id 10 from behaviors.utility.ballparks import get_specific_globals 11 from behaviors.utility.ballparks import is_target_valid, is_ball_cloaked, get_distance_between 12 from behaviors.utility.dogmatic import get_default_effect_id_for_type, get_my_attribute_value, type_has_effect 13 from behaviors.utility.dogmatic import get_entity_max_missile_range 14 from behaviors.utility.dogmatic import remove_my_target 15 from behaviors.utility.dogmatic import get_my_locked_targets, try_stop_effect 16 from behaviors.utility.inventory import get_inventory_item 17 from behaviors.utility.math import get_normalized_dict 18 from behaviors.utility.standings import is_hostile_towards 19 from dogma.const import attributeFalloff 20 from dogma.const import effectTargetAttack 21 from dogma.const import attributeMaxRange, effectMissileLaunchingForEntity 22 from dogma.effects.restricted.util import get_effect_range_with_falloff_multiplier 23 from dogma.effects.restricted.util import get_effect_range_and_falloff_values_for_item 24 from inventorycommon.const import categoryShip, categoryStructure 25 import uthread2 26 from behaviors.const.behaviorroles import COMBAT_DISTRIBUTE_EFFECTS_BY_ROLE, COMBAT_IGNORE_PRIMARY_BY_ROLE 27 from behaviors.const.blackboardchannels import MY_COMBAT_TARGET 28 from behaviors.groups.mixin import GroupTaskMixin 29 from behaviors.targetEvaluation import targetEvaluationRegistry, ThreatTargetEvaluator 30 from behaviors.tasks import Task 31 from behaviors.trees.role import ROLE_ADDRESS 32 from eve.common.script.sys import idCheckers 33 from evemath import weighted_choice 34 from ccpProfile import TimedFunction 35 from eveexceptions import UserError 36 logger = logging.getLogger(__name__) 37 38 class FindTarget(Task, GroupTaskMixin): 39 40 def __init__(self, attributes = None): 41 super(FindTarget, self).__init__(attributes) 42 self.filterFunctions = None 43 44 @TimedFunction('behaviors::actions::combattargets::FindTarget::OnEnter') 45 def OnEnter(self): 46 self.SetTargetEvaluator() 47 targetList = self._GetPotentialTargets() 48 selectedTargetId = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 49 self.filterFunctions = self._GetFilterFunctions() 50 targetList = self.FilterValidTargets(targetList) 51 if self.ShouldPickNewTarget(selectedTargetId, targetList): 52 pickedTarget = self.PickTarget(targetList) 53 self._update_picked_target(pickedTarget) 54 self.SendBlackboardValue(self.attributes.selectedTargetAddress, pickedTarget) 55 self.SetStatusToSuccess() 56 else: 57 if selectedTargetId is not None: 58 self.SendBlackboardValue(self.attributes.selectedTargetAddress, None) 59 self.SetStatusToFailed() 60 61 def SetTargetEvaluator(self): 62 if not self.HasContextValue('targetEvaluator'): 63 evaluatorClass = self._GetTargetEvaluatorClass() 64 targetEvaluator = evaluatorClass(self.context, self.attributes) 65 self.SetContextValue('targetEvaluator', targetEvaluator) 66 67 def _GetTargetEvaluatorClass(self): 68 return targetEvaluationRegistry[self.attributes.targetEvaluationFunction] 69 70 def _GetPotentialTargets(self): 71 return self.GetLastBlackboardValue(self.attributes.potentialTargetListAddress) 72 73 def _GetFilterFunctions(self): 74 return [self._GetFilterBubbleFunction(), 75 self._FilterTargetCategory, 76 self._FilterCloaked, 77 self._FilterProtected, 78 self._FilterMaxRangeFromGuardObject] 79 80 def PickTarget(self, targetList): 81 evaluateTarget = self.GetTargetEvaluator().EvaluateTarget 82 sortedList = sorted(targetList, key=evaluateTarget) 83 return sortedList[-1] 84 85 def GetTargetEvaluator(self): 86 return self.GetContextValue('targetEvaluator') 87 88 @TimedFunction('behaviors::actions::combattargets::FindTarget::FilterValidTargets') 89 def FilterValidTargets(self, targetList): 90 if targetList is None: 91 return [] 92 validTargets = self._GetValidTargets(targetList) 93 validTargets = self._filter_valid_targets_for_fleet_spread(validTargets) 94 validTargets = self._FilterValidTargetsForBehaviorRole(validTargets) 95 return validTargets 96 97 @TimedFunction('behaviors::actions::combattargets::FindTarget::_GetValidTargets') 98 def _GetValidTargets(self, targetList): 99 validTargets = [] 100 for targetId in targetList: 101 if self._IsTargetValid(targetId): 102 validTargets.append(targetId) 103 104 return validTargets 105 106 def _GetBubbleIdAndBubbleCheckRestriction(self): 107 bubbleId = None 108 if getattr(self.attributes, 'bubbleIdAddress', None) is not None: 109 bubbleId = self.GetLastBlackboardValue(self.attributes.bubbleIdAddress) 110 onlyCheckLocalBubble = True 111 else: 112 onlyCheckLocalBubble = self.attributes.onlyCheckLocalBubble 113 if onlyCheckLocalBubble: 114 bubbleId = self.context.myBall.newBubbleId 115 return (bubbleId, onlyCheckLocalBubble) 116 117 def _IsTargetValid(self, targetId): 118 ballpark = self.context.ballpark 119 ball = ballpark.balls.get(targetId) 120 if ball is None: 121 return False 122 return all((filterfunction(ballpark, ball) for filterfunction in self.filterFunctions)) 123 124 def _GetFilterBubbleFunction(self): 125 bubbleId, onlyCheckLocalBubble = self._GetBubbleIdAndBubbleCheckRestriction() 126 127 def _FilterBubble(_, ball): 128 if onlyCheckLocalBubble and ball.newBubbleId != bubbleId: 129 return False 130 return True 131 132 return _FilterBubble 133 134 def _FilterTargetCategory(self, ballpark, ball): 135 if self.attributes.includedCategories: 136 slimItem = ballpark.slims.get(ball.id) 137 if slimItem is None: 138 return False 139 if slimItem.categoryID not in self.attributes.includedCategories: 140 return False 141 return True 142 143 def _FilterCloaked(self, ballpark, ball): 144 if ball.isCloaked: 145 return False 146 return not ballpark.HasPendingCloak(ball.id) 147 148 def _FilterProtected(self, ballpark, ball): 149 return not ballpark.IsBallProtected(ball.id) 150 151 def _FilterMaxRangeFromGuardObject(self, ballpark, ball): 152 if not self.HasAttribute('guardObjectIdAddress'): 153 return True 154 guardObjectMaxRange = getattr(self.attributes, 'maxDistanceFromGuardObject', None) 155 if guardObjectMaxRange is not None: 156 guardObjectId = self.GetLastBlackboardValue(self.attributes.guardObjectIdAddress) 157 distanceBetween = get_distance_between(self, ball.id, guardObjectId) 158 if distanceBetween is not None and distanceBetween >= guardObjectMaxRange: 159 return False 160 return True 161 162 def ShouldPickNewTarget(self, selectedTarget, targetList): 163 if not targetList: 164 shouldPickNewTarget = False 165 elif selectedTarget is not None and selectedTarget in targetList: 166 shouldPickNewTarget = False 167 else: 168 shouldPickNewTarget = True 169 return shouldPickNewTarget 170 171 @TimedFunction('behaviors::actions::combattargets::FindTarget::_FilterValidTargetsForBehaviorRole') 172 def _FilterValidTargetsForBehaviorRole(self, validTargets): 173 myRoleAddress = getattr(self.attributes, 'roleAddress', None) 174 if not myRoleAddress: 175 return validTargets 176 myRole = self.GetLastBlackboardValue(self.attributes.roleAddress) 177 if not myRole: 178 return validTargets 179 validTargets = self._FilterPrimary(myRole, validTargets) 180 validTargets = self._FilterValidTargetsForEffectDistribution(myRole, validTargets) 181 return validTargets 182 183 @TimedFunction('behaviors::actions::combattargets::FindTarget::_FilterPrimary') 184 def _FilterPrimary(self, myRole, validTargets): 185 if len(validTargets) <= 1: 186 return validTargets 187 if self._ShouldIgnorePrimary(myRole): 188 if getattr(self.attributes, 'primaryTargetIdAddress', None): 189 primaryTargetId = self.GetLastBlackboardValue(self.attributes.primaryTargetIdAddress) 190 if primaryTargetId and primaryTargetId in validTargets: 191 validTargets.remove(primaryTargetId) 192 return validTargets 193 194 def _ShouldIgnorePrimary(self, myRole): 195 return myRole in COMBAT_IGNORE_PRIMARY_BY_ROLE 196 197 @TimedFunction('behaviors::actions::combattargets::FindTarget::_FilterValidTargetsForEffectDistribution') 198 def _FilterValidTargetsForEffectDistribution(self, myRole, validTargets): 199 if not self._ShouldDistributeEffects(myRole): 200 return validTargets 201 alreadyEffectedTargets = self._GetAllTargetsForGroupMembersWithRole(myRole) 202 availableTargets = list(set(validTargets) - alreadyEffectedTargets) 203 return availableTargets or validTargets 204 205 def _ShouldDistributeEffects(self, myRole): 206 return myRole in COMBAT_DISTRIBUTE_EFFECTS_BY_ROLE 207 208 def _GetAllTargetsForGroupMembersWithRole(self, role): 209 memberIds = self.GetMemberIdList() 210 alreadyEffectedTargets = set() 211 for groupMemberId in memberIds: 212 if groupMemberId == self.context.myItemId: 213 continue 214 targetId = self._GetTargetIdForGroupMemberWithRole(groupMemberId, role) 215 if targetId: 216 alreadyEffectedTargets.add(targetId) 217 218 return alreadyEffectedTargets 219 220 def _GetTargetIdForGroupMemberWithRole(self, groupMemberId, role): 221 try: 222 memberRole = self._GetChannelValueForGroupMemberId(groupMemberId, ROLE_ADDRESS[1]) 223 if memberRole == role: 224 return self._GetChannelValueForGroupMemberId(groupMemberId, MY_COMBAT_TARGET[1]) 225 except BlackboardDeletedError: 226 return None 227 228 def _GetChannelValueForGroupMemberId(self, groupMemberId, channelAddressName): 229 return self.GetMessageChannelForItemId(groupMemberId, channelAddressName).GetLastMessageValue() 230 231 @TimedFunction('behaviors::actions::combattargets::FindTarget::_filter_valid_targets_for_fleet_spread') 232 def _filter_valid_targets_for_fleet_spread(self, valid_targets): 233 if not self._should_spread_targets_in_fleet(): 234 return valid_targets 235 available_targets = valid_targets[:] 236 fleet_targets_by_group = self.GetLastBlackboardValue(self.attributes.fleetTargetsByGroupAddress) or {} 237 for entity_group_id, fleet_target_id in fleet_targets_by_group.iteritems(): 238 if entity_group_id == self.context.myEntityGroupId: 239 continue 240 if fleet_target_id in available_targets: 241 available_targets.remove(fleet_target_id) 242 243 return available_targets or valid_targets 244 245 def _should_spread_targets_in_fleet(self): 246 return self.HasAttribute('shouldSpreadTargetsInFleet') and self.attributes.shouldSpreadTargetsInFleet is True and self.HasAttribute('fleetTargetsByGroupAddress') 247 248 def _update_picked_target(self, picked_target_id): 249 self.SendBlackboardValue(self.attributes.selectedTargetAddress, picked_target_id) 250 if self.HasAttribute('fleetTargetsByGroupAddress'): 251 fleet_targets_by_group = self.GetLastBlackboardValue(self.attributes.fleetTargetsByGroupAddress) or {} 252 fleet_targets_by_group[self.context.myEntityGroupId] = picked_target_id 253 self.SendBlackboardValue(self.attributes.fleetTargetsByGroupAddress, fleet_targets_by_group) 254 255 256 class FindTargetForModule(FindTarget): 257 258 def __init__(self, attributes = None): 259 FindTarget.__init__(self, attributes) 260 261 def _GetPotentialTargets(self): 262 if self.HasAttribute('potentialTargetDictionaryAddress'): 263 primary_targets_by_entity = self.GetLastBlackboardValue(self.attributes.potentialTargetDictionaryAddress) 264 if primary_targets_by_entity: 265 return [ primary_target_id for primary_target_id in primary_targets_by_entity.itervalues() if primary_target_id is not None ] 266 return super(FindTargetForModule, self)._GetPotentialTargets() 267 268 def _GetFilterFunctions(self): 269 return [self._GetFilterBubbleFunction(), 270 self._FilterTargetCategory, 271 self._FilterCloaked, 272 self._FilterMaxRangeFromGuardObject, 273 self._get_module_range_filter_function()] 274 275 @TimedFunction('behaviors::actions::combattargets::FindTargetForModule::FilterValidTargets') 276 def FilterValidTargets(self, targetList): 277 if not targetList: 278 return [] 279 validTargets = self._GetValidTargets(targetList) 280 validTargets = self._FilterValidTargetsForBehaviorRole(validTargets) 281 validTargets = self._filter_valid_targets_for_module_distribution(validTargets) 282 return validTargets 283 284 def _get_module_range_filter_function(self): 285 module_item_id = self.GetLastBlackboardValue(self.attributes.moduleAddress) 286 module_type_id = get_inventory_item(self, module_item_id).typeID 287 effect_id = get_default_effect_id_for_type(self, module_type_id) 288 minimum_range = get_effect_range_with_falloff_multiplier(self.context.dogmaLM, effect_id, module_item_id, COMBAT_MAX_EFFECTIVE_FALLOFF_DISTANCE_FACTOR) 289 290 def _filter_module_range(_, ball): 291 return is_ball_in_range(self, self.context.myBall.id, ball.id, minimum_range) 292 293 return _filter_module_range 294 295 @TimedFunction('behaviors::actions::combattargets::FindTargetForModule::_filter_valid_targets_for_module_distribution') 296 def _filter_valid_targets_for_module_distribution(self, valid_targets): 297 targets_already_affected = self._get_my_already_affected_targets(valid_targets) 298 if not targets_already_affected: 299 return valid_targets 300 not_affected_targets = list(set(valid_targets).difference(targets_already_affected)) 301 if not_affected_targets: 302 return not_affected_targets 303 return [random.choice([min(targets_already_affected.keys(), key=targets_already_affected.get)])] 304 305 def _get_my_already_affected_targets(self, valid_targets): 306 targets_already_affected = defaultdict(int) 307 non_stackable_module_list = self._get_my_non_stackable_module_list() 308 if not non_stackable_module_list: 309 return targets_already_affected 310 my_targets_by_modules = self._get_my_targets_by_modules() or {} 311 for module_id, target_id in my_targets_by_modules.iteritems(): 312 if target_id is None or target_id not in valid_targets: 313 continue 314 module_type_id = get_inventory_item(self, module_id).typeID 315 if module_type_id in non_stackable_module_list: 316 targets_already_affected[target_id] += 1 317 318 return targets_already_affected 319 320 def _get_my_non_stackable_module_list(self): 321 eve_type_list_id = self.attributes.nonStackableEveTypeListId 322 if not eve_type_list_id: 323 return None 324 return evetypes.GetTypeIDsByListID(eve_type_list_id) 325 326 def _get_my_targets_by_modules(self): 327 return self.GetLastBlackboardValue(self.attributes.targetsByModulesAddress) 328 329 330 class AcquireTargetLock(Task): 331 332 def __init__(self, *args): 333 super(AcquireTargetLock, self).__init__(*args) 334 self._targetingThread = None 335 336 @TimedFunction('behaviors::actions::combattargets::AcquireTargetLock::OnEnter') 337 def OnEnter(self): 338 pass 339 340 @TimedFunction('behaviors::actions::combattargets::AcquireTargetLock::Update') 341 def Update(self): 342 self._AttemptToAcquireTargetLock() 343 344 def _AttemptToAcquireTargetLock(self): 345 selectedTarget = self.GetLastBlackboardValue(self.attributes.selectedTargetAddress) 346 if not self._IsTargetValid(selectedTarget): 347 self.SetStatusToFailed() 348 elif is_invulnerable(self, self.context.myItemId): 349 self._SetStatusWhileLockPending() 350 elif self.context.dogmaLM.IsTargeting(self.context.myItemId, selectedTarget): 351 self.SetStatusToSuccess() 352 elif self.context.dogmaLM.HasPendingTarget(self.context.myItemId, selectedTarget): 353 if self._targetingThread is None: 354 self._SetStatusWhileLockPending() 355 else: 356 self._SetStatusWhileWaitingForLock() 357 else: 358 self._SetStatusWhileWaitingForLock() 359 self._targetingThread = uthread2.start_tasklet(self._TryTargetLock, selectedTarget) 360 361 def _IsTargetValid(self, selectedTarget): 362 if selectedTarget is None: 363 return False 364 if not is_ball_in_park(self, selectedTarget): 365 return False 366 if is_ball_cloaked(self, selectedTarget): 367 return False 368 return True 369 370 def _SetStatusWhileWaitingForLock(self): 371 self.SubscribeItem(self.context.myItemId, MESSAGE_TARGET_ADDED, self.OnTargetAdded) 372 self.SetStatusToSuspended() 373 374 def _SetStatusWhileLockPending(self): 375 self.SubscribeItem(self.context.myItemId, MESSAGE_TARGET_ADDED, self.OnTargetAdded) 376 self.SetStatusToRunning() 377 378 @TimedFunction('behaviors::actions::combattargets::AcquireTargetLock::_TryTargetLock') 379 def _TryTargetLock(self, selectedTarget): 380 try: 381 self._LockTarget(selectedTarget) 382 except (UserError, RuntimeError) as e: 383 logger.debug('unable to acquire target lock: %s', e) 384 except TaskletExit: 385 logger.debug('The target locking thread got killed prematurely.') 386 finally: 387 if self.IsSuspended(): 388 self.SetStatusToRunning() 389 self.status.OnUpdated(self) 390 self._targetingThread = None 391 392 def _LockTarget(self, selectedTarget): 393 self.context.dogmaLM.AddTargetEx(self.context.myItemId, selectedTarget) 394 395 def OnTargetAdded(self, target_id): 396 self.SetStatusToSuccess() 397 self.status.OnUpdated(self) 398 399 def CleanUp(self): 400 if not self.IsInvalid(): 401 self.UnsubscribeItem(self.context.myItemId, MESSAGE_TARGET_ADDED, self.OnTargetAdded) 402 super(AcquireTargetLock, self).CleanUp() 403 404 405 class AcquireTargetLockNonBlocking(AcquireTargetLock): 406 407 def _SetStatusWhileWaitingForLock(self): 408 self.SetStatusToSuccess() 409 410 def _SetStatusWhileLockPending(self): 411 self.SetStatusToSuccess() 412 413 414 class UnlockTarget(Task): 415 416 @TimedFunction('behaviors::actions::combattargets::UnlockTarget::OnEnter') 417 def OnEnter(self): 418 targetList = self.GetTargetList() 419 if targetList: 420 targetId = targetList[0] 421 self.UnlockTarget(targetId) 422 self.SetStatusToSuccess() 423 424 def GetTargetList(self): 425 return self.context.dogmaLM.GetTargetsEx(self.context.myItemId) 426 427 def UnlockTarget(self, targetId): 428 self.context.dogmaLM.RemoveTarget(self.context.myItemId, targetId) 429 430 431 class UnlockModuleTarget(UnlockTarget): 432 433 @TimedFunction('behaviors::actions::combattargets::UnlockTarget::OnEnter') 434 def OnEnter(self): 435 target_list = self.GetTargetList() 436 target_id = self._get_target_id() 437 if target_list and target_id and target_id in target_list: 438 self.UnlockTarget(target_id) 439 self.SetStatusToSuccess() 440 441 def _get_target_id(self): 442 module_id = self.GetLastBlackboardValue(self.attributes.moduleAddress) 443 targets_by_modules = self.GetLastBlackboardValue(self.attributes.targetsByModulesAddress) 444 if targets_by_modules is None: 445 return 446 if module_id not in targets_by_modules: 447 return 448 target_id = targets_by_modules[module_id] 449 if target_id is None or self._is_also_targeted_by_other_module(module_id, target_id, targets_by_modules): 450 return 451 return target_id 452 453 def _is_also_targeted_by_other_module(self, module_id, target_id, targets_by_modules): 454 for other_module_id, other_target_id in targets_by_modules.iteritems(): 455 if other_module_id == module_id: 456 continue 457 if other_target_id == target_id: 458 return True 459 460 return False 461 462 463 class PruneNpcsFromTargetSet(Task, GroupTaskMixin): 464 465 def OnEnter(self): 466 self.SetStatusToSuccess() 467 targetSet = self.GetLastBlackboardValue(self.attributes.targetSetAddress) 468 if not targetSet: 469 return 470 removedSet = set() 471 destinyBalls = self.context.ballpark.balls 472 for targetId in targetSet: 473 if idCheckers.IsFakeItem(targetId) and targetId not in destinyBalls: 474 removedSet.add(targetId) 475 476 if removedSet: 477 targetSet.difference_update(removedSet) 478 self.SendBlackboardValue(self.attributes.targetSetAddress, targetSet) 479 480 481 class ClearTargetSet(Task, GroupTaskMixin): 482 483 def OnEnter(self): 484 self.SetStatusToSuccess() 485 targetSet = self.GetLastBlackboardValue(self.attributes.targetSetAddress) 486 if not targetSet: 487 return 488 self._UpdateTargetSet(set()) 489 entityGroupMembers = self.GetMemberIdList() 490 uthread2.start_tasklet(self._LogKilledAndFledTargets, targetSet, entityGroupMembers) 491 492 def _LogKilledAndFledTargets(self, targetSet, entityGroupMembers): 493 self.context.eventLogger.log_killed_and_fled_players(self.GetGroupOwnerId(), self.behaviorTree.GetBehaviorId(), targetSet, entityGroupMembers) 494 495 def _UpdateTargetSet(self, targetSet): 496 self.SendBlackboardValue(self.attributes.targetSetAddress, targetSet) 497 498 499 class SelectPilotedShips(Task): 500 501 @TimedFunction('behaviors::actions::combattargets::SelectPilotedShips::OnEnter') 502 def OnEnter(self): 503 self.SetStatusToSuccess() 504 target_ids = self.GetLastBlackboardValue(self.attributes.sourceSetAddress) 505 if not target_ids: 506 return 507 piloted_ship_set = self.get_piloted_ship_set(target_ids) 508 self.SendBlackboardValue(self.attributes.targetSetAddress, piloted_ship_set) 509 510 def get_piloted_ship_set(self, target_ids): 511 result_set = set() 512 for target_id in target_ids: 513 if self.is_valid(target_id): 514 result_set.add(target_id) 515 516 return result_set 517 518 def is_valid(self, target_id): 519 if not is_ball_in_park(self, target_id): 520 return False 521 slim_item = get_slim_item(self, target_id) 522 if slim_item is None: 523 return False 524 if slim_item.categoryID != categoryShip: 525 return False 526 if not self.context.dogmaLM.IsShipPiloted(target_id): 527 return False 528 return True 529 530 531 class GetMyMinOptimalAndFalloff(Task): 532 533 @TimedFunction('behaviors::actions::combattargets::GetMyMinOptimalAndFalloff::OnEnter') 534 def OnEnter(self): 535 self.SetStatusToSuccess() 536 min_optimal, min_falloff = self._get_my_min_optimal_and_falloff() 537 self.SendBlackboardValue(self.attributes.minOptimalAddress, min_optimal) 538 self.SendBlackboardValue(self.attributes.minFalloffAddress, min_falloff) 539 540 def _get_my_min_optimal_and_falloff(self): 541 effect_optimal, effect_falloff = self._get_my_min_effect_range() 542 weapon_optimal, weapon_falloff = self._get_my_min_weapon_range() 543 if not weapon_optimal or effect_optimal and (effect_optimal < weapon_optimal or effect_optimal == weapon_optimal and effect_falloff < weapon_falloff): 544 logger.debug('Behavior=%s for entity_type=%s setting min optimal=%s and falloff=%s from combat effect', self.behaviorTree.GetBehaviorId(), self.context.mySlimItem.typeID, effect_optimal, effect_falloff) 545 return (effect_optimal, effect_falloff) 546 logger.debug('Behavior=%s for entity_type=%s setting min optimal=%s and falloff=%s from weapons', self.behaviorTree.GetBehaviorId(), self.context.mySlimItem.typeID, weapon_optimal, weapon_falloff) 547 return (weapon_optimal, weapon_falloff) 548 549 def _get_my_min_effect_range(self): 550 minimum_optimal = 0.0 551 minimum_falloff = 0.0 552 my_combat_effects = self._get_my_combat_effects() 553 for effect_id in my_combat_effects: 554 if type_has_effect(self, self.context.mySlimItem.typeID, effect_id): 555 effect_optimal, effect_falloff = self._get_effect_optimal_and_falloff(effect_id) 556 if effect_optimal == 0.0: 557 logger.error('Badly authored effect: %s for behavior NPC range picks - no range assigned to it.', effect_id) 558 continue 559 if minimum_optimal == 0.0 or effect_optimal < minimum_optimal or effect_optimal == minimum_optimal and effect_falloff < minimum_falloff: 560 minimum_optimal = effect_optimal 561 minimum_falloff = effect_falloff 562 563 return (minimum_optimal, minimum_falloff) 564 565 def _get_my_combat_effects(self): 566 return self.GetLastBlackboardValue(self.attributes.combatEffectsAddress) or [] 567 568 def _get_effect_optimal_and_falloff(self, effect_id): 569 effect_optimal, effect_falloff = get_effect_range_and_falloff_values_for_item(self.context.dogmaLM, self.context.myItemId, effect_id) 570 return (effect_optimal or 0.0, effect_falloff or 0.0) 571 572 def _get_my_min_weapon_range(self): 573 weapon_optimal, weapon_falloff = turret_optimal, turret_falloff = self._get_turret_range() 574 if type_has_effect(self, self.context.mySlimItem.typeID, effectMissileLaunchingForEntity): 575 missile_range = self._get_missile_range() 576 if not turret_optimal or missile_range < turret_optimal: 577 weapon_optimal = missile_range 578 weapon_falloff = 0.0 579 return (weapon_optimal or 0.0, weapon_falloff or 0.0) 580 581 def _get_turret_range(self): 582 weapon_optimal = get_my_attribute_value(self, attributeMaxRange) 583 weapon_falloff = get_my_attribute_value(self, attributeFalloff) 584 if weapon_falloff == 1.0: 585 weapon_falloff = 0.0 586 return (weapon_optimal, weapon_falloff) 587 588 def _get_missile_range(self): 589 return get_entity_max_missile_range(self, self.context.mySlimItem.typeID) 590 591 592 class GetMaxEffectiveRange(Task): 593 594 @TimedFunction('behaviors::actions::combattargets::GetMaxEffectiveRange::OnEnter') 595 def OnEnter(self): 596 self.SetStatusToSuccess() 597 max_effective_range = self._get_max_effective_range() 598 self.SendBlackboardValue(self.attributes.maxEffectiveRangeAddress, max_effective_range) 599 600 def _get_max_effective_range(self): 601 optimal_range = self.GetLastBlackboardValue(self.attributes.minOptimalAddress) 602 falloff_range = self.GetLastBlackboardValue(self.attributes.minFalloffAddress) or 0 603 if falloff_range: 604 max_effective_range = optimal_range + falloff_range * COMBAT_MAX_EFFECTIVE_FALLOFF_DISTANCE_FACTOR 605 return max_effective_range * COMBAT_MAX_EFFECTIVE_DISTANCE_FACTOR 606 else: 607 return optimal_range 608 609 610 class GenerateTargetSetsByCombatRangesInGroup(Task): 611 612 @TimedFunction('behaviors::actions::combattargets::GenerateTargetSetsByCombatRangesInGroup::OnEnter') 613 def OnEnter(self): 614 self.SetStatusToSuccess() 615 targets_by_entity_type = self._get_targets_by_entity_type() 616 logger.debug('Behavior=%s for task=%s generated target_set=%s', self.behaviorTree.GetBehaviorId(), self.GetTaskName(), targets_by_entity_type) 617 self.SendBlackboardValue(self.attributes.targetsByEntityTypeAddress, targets_by_entity_type) 618 619 def _get_targets_by_entity_type(self): 620 range_by_entity_type = self._get_range_by_entity_type() 621 balls_in_range = self._get_ball_ids_and_distance_in_range(range_by_entity_type) 622 targets_by_entity_type = defaultdict(set) 623 threat_target_set = self._get_threat_target_set() 624 for distance_squared, ball_id in balls_in_range: 625 if not is_target_valid(self, ball_id, target_list=threat_target_set, required_categories=[categoryShip]): 626 continue 627 self._add_target(targets_by_entity_type, range_by_entity_type, ball_id, distance_squared) 628 629 return targets_by_entity_type 630 631 def _get_range_by_entity_type(self): 632 return self.GetLastBlackboardValue(self.attributes.rangeByEntityTypeAddress) 633 634 def _get_ball_ids_and_distance_in_range(self, range_by_entity_type): 635 if range_by_entity_type is None: 636 return [] 637 commander = self._get_commander() 638 if commander is None or not is_ball_in_park(self, commander): 639 logger.warn('Behavior=%s for task=% is missing commander', self.behaviorTree.GetBehaviorId(), self.GetTaskName()) 640 return [] 641 max_effective_range_of_group = max(range_by_entity_type.values()) 642 return self.context.ballpark.GetBallIdsAndDistInRange(commander, max_effective_range_of_group) 643 644 def _get_commander(self): 645 return self.GetLastBlackboardValue(self.attributes.commanderAddress) 646 647 def _get_threat_target_set(self): 648 if self.HasAttribute('threatTargetSetAddres'): 649 return self.GetLastBlackboardValue(self.attributes.threatTargetSetAddress) 650 651 def _add_target(self, targets_by_entity_type, range_by_entity_type, ball_id, distance_squared): 652 for entity_type_id, max_effective_range in range_by_entity_type.iteritems(): 653 if max_effective_range ** 2 > distance_squared: 654 targets_by_entity_type[entity_type_id].add(ball_id) 655 656 657 class GetPrimaryAndNonPrimaryTargetsByEntityTypes(Task): 658 659 @TimedFunction('behaviors::actions::combattargets::GetTargetsByRanges::OnEnter') 660 def OnEnter(self): 661 self.SetStatusToSuccess() 662 primary_targets, non_primary_targets = self._get_targets_by_entity_type() 663 self.SetStatusToSuccess() 664 self._post_targets_by_entity_type(primary_targets, non_primary_targets) 665 666 def _get_targets_by_entity_type(self): 667 all_targets = self._get_all_targets() 668 if not all_targets: 669 return (None, None) 670 primary_evaluator = self._get_primary_evaluator() 671 primary_targets = {} 672 non_primary_evaluator = self._get_non_primary_evaluator() 673 non_primary_targets = {} 674 for entity_type, targets_set in all_targets.iteritems(): 675 self._get_primary_target(primary_targets, primary_evaluator, entity_type, targets_set) 676 primary = primary_targets[entity_type] 677 self._get_non_primary_targets(non_primary_targets, non_primary_evaluator, entity_type, targets_set, primary) 678 679 return (primary_targets, non_primary_targets) 680 681 def _get_all_targets(self): 682 return self.GetLastBlackboardValue(self.attributes.targetsAddress) 683 684 def _get_primary_evaluator(self): 685 evaluator_class = targetEvaluationRegistry[self.attributes.primaryTargetEvaluationFunction] 686 return evaluator_class(self.context, self.attributes) 687 688 def _get_non_primary_evaluator(self): 689 evaluator_class = targetEvaluationRegistry[self.attributes.nonPrimaryTargetEvaluationFunction] 690 return evaluator_class(self.context, self.attributes) 691 692 def _get_primary_target(self, primary_targets, primary_target_evaluator, entity_type, targets_set): 693 type_list_id = getattr(self.attributes, 'primaryTypeListId', None) 694 filtered_targets_set = self._filter_targets_set_by_type_list(type_list_id, targets_set) 695 if filtered_targets_set: 696 primary_targets[entity_type] = sorted(filtered_targets_set, key=primary_target_evaluator.EvaluateTarget)[-1] 697 else: 698 primary_targets[entity_type] = None 699 700 def _get_non_primary_targets(self, non_primary_targets, non_primary_evaluator, entity_type, targets_set, primary): 701 type_list_id = getattr(self.attributes, 'nonPrimaryTypeListId', None) 702 filtered_targets_set = self._filter_targets_set_by_type_list(type_list_id, targets_set) 703 if primary and primary in filtered_targets_set: 704 filtered_targets_set.remove(primary) 705 non_primary_targets[entity_type] = set() 706 for target_id in filtered_targets_set: 707 weight = non_primary_evaluator.EvaluateTarget(target_id) 708 non_primary_targets[entity_type].add((target_id, weight)) 709 710 def _filter_targets_set_by_type_list(self, type_list_id, targets_set): 711 if type_list_id is None: 712 return targets_set 713 types_of_interest = evetypes.GetTypeIDsByListID(type_list_id) 714 filtered_targets_set = set() 715 for target_id in targets_set: 716 if get_slim_item(self, target_id).typeID in types_of_interest: 717 filtered_targets_set.add(target_id) 718 719 return filtered_targets_set 720 721 def _post_targets_by_entity_type(self, primary_targets, non_primary_targets): 722 logger.debug('Behavior=%s for task=%s found and posting primary_targets=%s and non_primary=%s', self.behaviorTree.GetBehaviorId(), self.GetTaskName(), primary_targets, non_primary_targets) 723 self.SendBlackboardValue(self.attributes.primaryTargetsByEntityTypesAddress, primary_targets) 724 self.SendBlackboardValue(self.attributes.nonPrimaryTargetsByEntityTypesAddress, non_primary_targets) 725 726 727 class GetWeightedTargetFromTargetsSet(Task): 728 729 @TimedFunction('behaviors::actions::combattargets::GetWeightedTargetFromTargetsSet::OnEnter') 730 def OnEnter(self): 731 target_weight_set = self._get_target_weight_set() 732 target_id = self._get_target(target_weight_set) 733 if target_id is None: 734 self.SetStatusToFailed() 735 return 736 self.SetStatusToSuccess() 737 logger.debug('Behavior=%s for task=%s and entity=%s received new target=%s', self.behaviorTree.GetBehaviorId(), self.GetTaskName(), self.context.myItemId, target_id) 738 self._post_target(target_id) 739 740 def _get_target(self, target_weight_set): 741 if not target_weight_set: 742 return None 743 return weighted_choice(target_weight_set) 744 745 def _get_target_weight_set(self): 746 return self.GetLastBlackboardValue(self.attributes.targetWeightSetAddress) 747 748 def _post_target(self, target_id): 749 self.SendBlackboardValue(self.attributes.targetIdAddress, target_id) 750 751 752 class DisengageObsoleteTargets(Task): 753 754 @TimedFunction('behaviors::actions::combattargets::DisengageObsoleteTargets::OnEnter') 755 def OnEnter(self): 756 self.SetStatusToSuccess() 757 my_target_is = self.GetLastBlackboardValue(self.attributes.myTargetAddress) 758 my_locked_targets = get_my_locked_targets(self) 759 my_combat_effects = self._get_my_combat_effects() 760 for target_id in my_locked_targets: 761 if target_id != my_target_is: 762 self._stop_combat_effects_on_target(my_combat_effects) 763 remove_my_target(self, target_id) 764 765 def _get_my_combat_effects(self): 766 my_combat_effects = [effectTargetAttack, effectMissileLaunchingForEntity] 767 my_combat_effects_from_address = self.GetLastBlackboardValue(self.attributes.myCombatEffectsAddress) or [] 768 my_combat_effects.extend(my_combat_effects_from_address) 769 return my_combat_effects 770 771 def _stop_combat_effects_on_target(self, my_combat_effects): 772 for effect_id in my_combat_effects: 773 try_stop_effect(self, effect_id) 774 775 776 class FindHostileStructureInRange(Task): 777 778 @TimedFunction('behaviors::actions::combattargets::FindHostileStructureInRange::OnEnter') 779 def OnEnter(self): 780 self.SetStatusToFailed() 781 hostile_global_structures = self._get_hostile_structures() 782 if hostile_global_structures: 783 current_hostiles = self.GetLastBlackboardValue(self.attributes.targetsAddress) or set() 784 current_hostiles = current_hostiles.union(hostile_global_structures) 785 self.SendBlackboardValue(self.attributes.targetsAddress, current_hostiles) 786 self.SetStatusToSuccess() 787 logger.debug('Behavior=%s found hostile_structures=%s', self.context.myItemId, hostile_global_structures) 788 789 def _get_hostile_structures(self): 790 hostile_global_structures = set() 791 for global_ball_id, global_ball in get_specific_globals(self, category_id=categoryStructure).iteritems(): 792 if self._is_valid(global_ball_id, global_ball): 793 hostile_global_structures.add(global_ball_id) 794 795 return hostile_global_structures 796 797 def _is_valid(self, global_ball_id, global_ball): 798 if not global_ball.IsVulnerable(): 799 return False 800 if not is_hostile_towards(self, global_ball.ownerID): 801 return False 802 return is_ball_in_range(self, self.context.myItemId, global_ball_id, self.attributes.minDistance) 803 804 805 class FindTargetWithPolarizedPreference(FindTarget): 806 807 def __init__(self, attributes = None): 808 super(FindTargetWithPolarizedPreference, self).__init__(attributes) 809 810 def OnEnter(self): 811 super(FindTargetWithPolarizedPreference, self).OnEnter() 812 813 def _GetTargetEvaluatorClass(self): 814 return ThreatTargetEvaluator 815 816 def PickTarget(self, target_list): 817 weightByItemId = {itemId:self._EvaluateTarget(itemId) for itemId in target_list} 818 if self._IsPreferenceEnabled(): 819 weightByItemId = get_normalized_dict(weightByItemId) 820 biasAmount = self._GetPreferredTypeBiasAmount() 821 if self._IsPreferringType(): 822 self._UpdateWeights(weightByItemId, biasAmount) 823 else: 824 self._UpdateWeights(weightByItemId, -biasAmount) 825 sortedTargetIds = sorted(weightByItemId.keys(), key=lambda itemId: weightByItemId[itemId]) 826 return sortedTargetIds[-1] 827 828 def _UpdateWeights(self, weightByItemId, biasAmount): 829 preferredTypeList = self._GetPreferredTypeList() 830 for itemId in weightByItemId: 831 if self._GetBallTypeId(itemId) in preferredTypeList: 832 weightByItemId[itemId] += biasAmount 833 834 def _GetBallTypeId(self, itemId): 835 return get_ball_type_id(self, itemId) 836 837 def _GetPreferredTypeList(self): 838 preferredTypeList = evetypes.GetTypeIDsByListID(self.attributes.preferredTypeListID) 839 return preferredTypeList 840 841 def _IsPreferenceEnabled(self): 842 return self.GetLastBlackboardValue(self.attributes.isPreferenceEnabledAddress) 843 844 def _IsPreferringType(self): 845 isPreferringType = self._GetIsPreferringType() 846 if isPreferringType is None: 847 isPreferringType = self._GetChanceOfPreferringType() >= random.random() * 100 848 self._SendIsPreferringType(isPreferringType) 849 return isPreferringType 850 851 def _GetChanceOfPreferringType(self): 852 return self.GetLastBlackboardValue(self.attributes.chanceOfPreferringTypesAddress) 853 854 def _SendIsPreferringType(self, isPreferringType): 855 self.SendBlackboardValue(self.attributes.isPreferringTypeAddress, isPreferringType) 856 857 def _GetIsPreferringType(self): 858 return self.GetLastBlackboardValue(self.attributes.isPreferringTypeAddress) 859 860 def _EvaluateTarget(self, itemId): 861 return self.GetTargetEvaluator().EvaluateTarget(itemId) 862 863 def _GetPreferredTypeBiasAmount(self): 864 return self.GetLastBlackboardValue(self.attributes.preferredTypeBiasAmountAddress)