NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

logistics.py (17281B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\behaviors\actions\logistics.py
      2 from evemath import get_center_point
      3 from behaviors.blackboards import BlackboardDeletedError
      4 from behaviors.groups.mixin import GroupTaskMixin
      5 from behaviors.tasks import Task
      6 from behaviors.utility.ballparks import get_ball_position, is_ball_in_park, get_distance_between
      7 from behaviors.utility.dogmatic import get_my_attribute_value, is_assistance_disallowed, get_range_falloff_multiplier_at_distance
      8 from behaviors.utility.logistics import get_logistics_priority, get_remote_repair_effect_id, get_damage_state_function_by_effect_id
      9 from behaviors.utility.math import compute_rear_fleet_position, compute_forward_fleet_position
     10 from ccpProfile import TimedFunction
     11 from collections import Counter
     12 from dogma.const import attributeNPCAssistanceRange, effectBehaviorNPCRemoteArmorRepair
     13 from dogma.const import attributeMaxTargetRange, attributeMaxLockedTargets
     14 from dogma.effects.restricted.util import get_effect_max_effective_range_with_falloff
     15 from eveexceptions import UserError
     16 from inventorycommon.const import categoryEntity, categoryDrone, categoryShip
     17 from inventorycommon import const as invConst, WrongInventoryLocation, FakeItemNotHere
     18 import logging
     19 from threadutils.be_nice import be_nice
     20 import uthread2
     21 logger = logging.getLogger(__name__)
     22 LOGISTICS_CATEGORIES_TO_CONSIDER = [categoryEntity, categoryShip, categoryDrone]
     23 LOGISTICS_GROUPS_TO_IGNORE = {invConst.groupSpawnContainer,
     24  invConst.groupSpewContainer,
     25  invConst.groupBillboard,
     26  invConst.groupAgentsinSpace,
     27  invConst.groupCapturePointTower,
     28  invConst.groupControlBunker,
     29  invConst.groupLargeCollidableShip,
     30  invConst.groupLargeCollidableObject,
     31  invConst.groupLargeCollidableStructure,
     32  invConst.groupTemporaryCollidableStructure,
     33  invConst.groupMissionContainer,
     34  invConst.groupTemporaryCloud,
     35  invConst.groupSentryGun,
     36  invConst.groupIndustrialSupportFacility}
     37 LOGISTICS_EFFECTS = (effectBehaviorNPCRemoteArmorRepair,)
     38 
     39 class SelectDesiredAnchorPosition(Task, GroupTaskMixin):
     40 
     41     @TimedFunction('behaviors::actions::logistics::SelectDesiredAnchorPosition::OnEnter')
     42     def OnEnter(self):
     43         friendlyPositionList, hostilePositionList = self.CollectFriendlyAndHostilePositions()
     44         try:
     45             if not hostilePositionList:
     46                 desiredPosition = get_center_point(friendlyPositionList)
     47             else:
     48                 desiredPosition = self.ComputeDesiredPosition(friendlyPositionList, hostilePositionList)
     49             self.SendBlackboardValue(self.attributes.desiredPositionAddress, tuple(desiredPosition))
     50             self.SetStatusToSuccess()
     51         except ValueError:
     52             self.SendBlackboardValue(self.attributes.desiredPositionAddress, None)
     53             self.SetStatusToFailed()
     54 
     55     @TimedFunction('behaviors::actions::logistics::SelectDesiredAnchorPosition::OnEnter::CollectFriendlyAndHostilePositions')
     56     def CollectFriendlyAndHostilePositions(self):
     57         ballIds = self.GetBallIdsToConsider()
     58         friendlyPositionList = []
     59         hostilePositionList = []
     60         slims = self.context.ballpark.slims
     61         balls = self.context.ballpark.balls
     62         for itemId in ballIds:
     63             self.ClassifyItemIdAndCollect(itemId, balls, slims, friendlyPositionList, hostilePositionList)
     64 
     65         return (friendlyPositionList, hostilePositionList)
     66 
     67     def ClassifyItemIdAndCollect(self, itemId, balls, slims, friendlyPositionList, hostilePositionList):
     68         slimItem = slims.get(itemId)
     69         if not slimItem:
     70             return
     71         if slimItem.categoryID not in LOGISTICS_CATEGORIES_TO_CONSIDER:
     72             return
     73         if slimItem.groupID in LOGISTICS_GROUPS_TO_IGNORE:
     74             return
     75         if slimItem.ownerID in self.attributes.friendlyOwners:
     76             ball = balls[itemId]
     77             friendlyPositionList.append((ball.x, ball.y, ball.z))
     78         if slimItem.ownerID in self.attributes.hostileOwners:
     79             ball = balls[itemId]
     80             hostilePositionList.append((ball.x, ball.y, ball.z))
     81 
     82     def GetBallIdsToConsider(self):
     83         ballpark = self.context.ballpark
     84         ballIds = [ ballId for ballId in ballpark.bubbles[ballpark.balls[self.context.myItemId].newBubbleId] if ballId > 0 ]
     85         return ballIds
     86 
     87     @TimedFunction('behaviors::actions::logistics::SelectDesiredAnchorPosition::OnEnter::ComputeDesiredPosition')
     88     def ComputeDesiredPosition(self, friendlyPositionList, hostilePositionList):
     89         friendlyCentroid = get_center_point(friendlyPositionList)
     90         hostileCentroid = get_center_point(hostilePositionList)
     91         logisticsRange = self.GetLogisticsRange()
     92         desiredRearPosition = compute_rear_fleet_position(friendlyCentroid, hostileCentroid, logisticsRange * self.attributes.proximityFraction)
     93         return desiredRearPosition
     94 
     95     def GetLogisticsRange(self):
     96         ranges = self.GetGroupLogisticsRanges()
     97         if not ranges:
     98             raise ValueError('no valid logistics range found')
     99         return min(ranges)
    100 
    101     @TimedFunction('behaviors::actions::logistics::SelectDesiredAnchorPosition::OnEnter::GetGroupLogisticsRanges')
    102     def GetGroupLogisticsRanges(self):
    103         ranges = []
    104         for memberId in self.GetMemberIdList():
    105             repairRange = self.GetRepairRange(memberId)
    106             if repairRange > 0:
    107                 ranges.append(repairRange)
    108 
    109         return ranges
    110 
    111     def GetRepairRange(self, memberId):
    112         return self.context.dogmaLM.GetAttributeValue(memberId, attributeNPCAssistanceRange)
    113 
    114 
    115 class SelectGroupMemberBasedOnLowestAttributeValue(Task, GroupTaskMixin):
    116 
    117     @TimedFunction('behaviors::actions::logistics::SelectGroupMemberBasedOnLowestAttributeValue::OnEnter')
    118     def OnEnter(self):
    119         memberList = self.GetLogisticsMembers()
    120         if memberList:
    121             self.SortMemberByAttributeValue(memberList)
    122             self.SendBlackboardValue(self.attributes.selectedIdAddress, memberList[0])
    123             self.SetStatusToSuccess()
    124         else:
    125             self.SetStatusToFailed()
    126 
    127     @TimedFunction('behaviors::actions::logistics::SelectGroupMemberBasedOnLowestAttributeValue::SortMemberByAttributeValue')
    128     def SortMemberByAttributeValue(self, memberList):
    129         GetAttributeValue = self.context.dogmaLM.GetAttributeValue
    130         attributeId = self.attributes.attributeId
    131         memberList.sort(key=lambda memberId: GetAttributeValue(memberId, attributeId))
    132 
    133     @TimedFunction('behaviors::actions::logistics::SelectGroupMemberBasedOnLowestAttributeValue::GetLogisticsMembers')
    134     def GetLogisticsMembers(self):
    135         return [ memberId for memberId in self.GetMemberIdList() if self.HasLogisticsEffects(memberId) ]
    136 
    137     def HasLogisticsEffects(self, memberId):
    138         slimItem = self.context.ballpark.slims.get(memberId)
    139         if not slimItem:
    140             return False
    141         else:
    142             return self.HasAnyLogisticsEffects(slimItem.typeID)
    143 
    144     def HasAnyLogisticsEffects(self, typeID):
    145         for effectId in LOGISTICS_EFFECTS:
    146             if self.HasEffect(typeID, effectId):
    147                 return True
    148 
    149         return False
    150 
    151     def HasEffect(self, typeID, effectId):
    152         return self.context.dogmaLM.dogmaStaticMgr.TypeHasEffect(typeID, effectId)
    153 
    154 
    155 class SetRearFleetPosition(Task):
    156 
    157     @TimedFunction('behaviors::actions::logistics::SetRearFleetPosition::OnEnter')
    158     def OnEnter(self):
    159         self.SetStatusToFailed()
    160         hostilePosition = self.GetLastBlackboardValue(self.attributes.hostilePositionAddress)
    161         if not hostilePosition:
    162             return
    163         anchorBallId = self.GetLastBlackboardValue(self.attributes.anchorBallIdAddress)
    164         if not anchorBallId or not is_ball_in_park(self, anchorBallId):
    165             return
    166         friendlyPosition = get_ball_position(self, anchorBallId)
    167         distance = self.GetLastBlackboardValue(self.attributes.distanceAddress)
    168         rear_position = self.GetTargetPosition(friendlyPosition, hostilePosition, distance)
    169         self.SendBlackboardValue(self.attributes.targetPositionAddress, rear_position)
    170         self.SetStatusToSuccess()
    171 
    172     def GetTargetPosition(self, friendlyPosition, hostilePosition, distance):
    173         return compute_rear_fleet_position(friendlyPosition, hostilePosition, distance)
    174 
    175 
    176 class SetForwardFleetPosition(SetRearFleetPosition):
    177 
    178     def GetTargetPosition(self, friendlyPosition, hostilePosition, distance):
    179         return compute_forward_fleet_position(friendlyPosition, hostilePosition, distance)
    180 
    181 
    182 class PrioritizeLogisticsWatchlist(Task, GroupTaskMixin):
    183 
    184     def __init__(self, attributes = None):
    185         super(PrioritizeLogisticsWatchlist, self).__init__(attributes=attributes)
    186 
    187     @TimedFunction('behaviors::actions::logistics::PrioritizeLogisticsWatchlist::OnEnter')
    188     def OnEnter(self):
    189         self.SetStatusToSuccess()
    190         damage_state_function = self.get_damage_state_function()
    191         if damage_state_function is None:
    192             return
    193         watchlist = self.GetLastBlackboardValue(self.attributes.watchlistAddress)
    194         if watchlist:
    195             prioritized_watchlist = self._GetPrioritizedWatchlist(watchlist)
    196         else:
    197             prioritized_watchlist = []
    198         self.SendBlackboardValue(self.attributes.prioritizedWatchlistAddress, prioritized_watchlist)
    199 
    200     def get_repair_effect_id(self):
    201         remote_repair_effect_by_type = self.GetLastBlackboardValue(self.attributes.remoteRepairEffectRegistryAddress)
    202         if not remote_repair_effect_by_type:
    203             return None
    204         most_common = Counter(remote_repair_effect_by_type.values()).most_common(1)[0]
    205         return most_common[0]
    206 
    207     @TimedFunction('behaviors::actions::logistics::PrioritizeLogisticsWatchlist::_GetPrioritizedWatchlist')
    208     def _GetPrioritizedWatchlist(self, watchlist):
    209         repair_effect_id = self.get_repair_effect_id()
    210         if repair_effect_id is None:
    211             return []
    212         get_damage_state = get_damage_state_function_by_effect_id(repair_effect_id)
    213         prioritized_watchlist = []
    214         for item_id in watchlist:
    215             self.try_add_item_with_priority(item_id, prioritized_watchlist, get_damage_state)
    216 
    217         prioritized_watchlist.sort(reverse=True)
    218         return prioritized_watchlist
    219 
    220     def try_add_item_with_priority(self, item_id, prioritized_watchlist, get_damage_state):
    221         if not is_ball_in_park(self, item_id):
    222             return
    223         if is_assistance_disallowed(self, item_id):
    224             return
    225         try:
    226             priority = get_logistics_priority(self, item_id, get_damage_state)
    227             prioritized_watchlist.append([priority, item_id])
    228         except (WrongInventoryLocation,
    229          FakeItemNotHere,
    230          ZeroDivisionError,
    231          BlackboardDeletedError) as e:
    232             logger.debug('Behavior=%s for group=%s error while getting logistic priority: %s', self.behaviorTree.GetBehaviorId, self.context.myEntityGroupId, e)
    233 
    234     def get_damage_state_function(self):
    235         if not self.HasContextValue('get_damage_state'):
    236             remote_repair_effect_id = self.get_repair_effect_id()
    237             if remote_repair_effect_id is not None:
    238                 self.SetContextValue('get_damage_state', get_damage_state_function_by_effect_id(remote_repair_effect_id))
    239         return self.GetContextValue('get_damage_state')
    240 
    241 
    242 class LockPrioritizedWatchlistMembers(Task):
    243 
    244     def __init__(self, attributes = None):
    245         super(LockPrioritizedWatchlistMembers, self).__init__(attributes=attributes)
    246         self.is_updating_locks = False
    247 
    248     def _initialize(self):
    249         if not self.HasContextValue('remote_repair_effect_id'):
    250             if self.HasAttribute('remoteRepairEffectIdAddress'):
    251                 remote_repair_effect_id = self.GetLastBlackboardValue(self.attributes.remoteRepairEffectIdAddress)
    252             else:
    253                 remote_repair_effect_id = get_remote_repair_effect_id(self)
    254             self.SetContextValue('remote_repair_effect_id', remote_repair_effect_id)
    255         if self._has_combat_target():
    256             self.SetContextValue('reserved_combat_target_count', 1)
    257         else:
    258             self.SetContextValue('reserved_combat_target_count', 0)
    259 
    260     @TimedFunction('behaviors::actions::logistics::LockPrioritizedWatchlistMembers::OnEnter')
    261     def OnEnter(self):
    262         self._initialize()
    263         self.SetStatusToSuccess()
    264         prioritized_watchlist = self.GetLastBlackboardValue(self.attributes.prioritizedWatchlistAddress)
    265         if not prioritized_watchlist:
    266             return
    267         best_candidates_in_range = self._FilterItemsInTargetingRange(prioritized_watchlist)
    268         self._UpdateLockedTargets(best_candidates_in_range)
    269 
    270     def _get_effective_range(self):
    271         return get_effect_max_effective_range_with_falloff(self.context.dogmaLM, self.GetContextValue('remote_repair_effect_id'), self.context.myItemId)
    272 
    273     def get_max_targets(self):
    274         max_total_targets = int(get_my_attribute_value(self, attributeMaxLockedTargets))
    275         available_logistic_target_count = max_total_targets - self.GetContextValue('reserved_combat_target_count')
    276         return max(0, available_logistic_target_count)
    277 
    278     def _get_current_combat_target(self):
    279         if self._has_combat_target():
    280             return self.GetLastBlackboardValue(self.attributes.combatTargetAddress)
    281 
    282     def _has_combat_target(self):
    283         return self.HasAttribute('combatTargetAddress')
    284 
    285     @TimedFunction('behaviors::actions::logistics::LockPrioritizedWatchlistMembers::_FilterItemsInTargetingRange')
    286     def _FilterItemsInTargetingRange(self, prioritized_watchlist):
    287         my_item_id = self.context.myItemId
    288         target_range = get_my_attribute_value(self, attributeMaxTargetRange)
    289         effective_range = self._get_effective_range()
    290         useful_range = min(target_range, effective_range)
    291         new_prioritized_watchlist = []
    292         for p, item_id in prioritized_watchlist:
    293             if item_id == my_item_id:
    294                 continue
    295             distance = get_distance_between(self, self.context.myItemId, item_id)
    296             if distance < useful_range:
    297                 falloff_multiplier = get_range_falloff_multiplier_at_distance(self, self.GetContextValue('remote_repair_effect_id'), distance)
    298                 new_prioritized_watchlist.append((p * falloff_multiplier, item_id))
    299 
    300         new_prioritized_watchlist.sort(reverse=True)
    301         max_targets = self.get_max_targets()
    302         return [ item_id for _, item_id in new_prioritized_watchlist[:max_targets] ]
    303 
    304     @TimedFunction('behaviors::actions::logistics::LockPrioritizedWatchlistMembers::_UpdateLockedTargets')
    305     def _UpdateLockedTargets(self, items_to_target):
    306         locked_targets = set(self.context.dogmaLM.GetTargets(self.context.myItemId))
    307         self._remove_combat_target(locked_targets)
    308         targets_to_unlock = locked_targets.difference(items_to_target)
    309         targets_to_lock = set(items_to_target).difference(locked_targets)
    310         uthread2.start_tasklet(self._UnlockAndLockTargets_Thread, targets_to_unlock, targets_to_lock)
    311 
    312     def _remove_combat_target(self, locked_targets):
    313         combat_target = self._get_current_combat_target()
    314         if combat_target:
    315             locked_targets.discard(combat_target)
    316 
    317     @TimedFunction('behaviors::actions::logistics::LockPrioritizedWatchlistMembers::_UnlockAndLockTargets_Thread')
    318     def _UnlockAndLockTargets_Thread(self, targets_to_unlock, targets_to_lock):
    319         if self.is_updating_locks:
    320             return
    321         try:
    322             self.is_updating_locks = True
    323             for item_id in targets_to_unlock:
    324                 self.context.dogmaLM.RemoveTarget(self.context.myItemId, item_id)
    325                 be_nice()
    326 
    327             for item_id in targets_to_lock:
    328                 if not self.context.dogmaLM.HasPendingTarget(self.context.myItemId, item_id):
    329                     self._TryLockTarget(item_id)
    330                 be_nice()
    331 
    332         finally:
    333             self.is_updating_locks = False
    334 
    335     def _TryLockTarget(self, item_id):
    336         try:
    337             self.context.dogmaLM.AddTargetEx(self.context.myItemId, item_id, tasklet=True)
    338         except (UserError, RuntimeError) as e:
    339             logger.debug('LockPrioritizedWatchlistMembers: %s targeting %s: Unable to acquire target lock: %s', self.context.myItemId, item_id, e)
    340         except TaskletExit:
    341             logger.debug('LockPrioritizedWatchlistMembers: %s targeting %s: The target locking thread got killed prematurely', self.context.myItemId, item_id)
    342         except Exception as e:
    343             logger.debug('LockPrioritizedWatchlistMembers: %s targeting %s: something else happened: %s', self.context.myItemId, item_id, e)
    344 
    345 
    346 class RegisterRepairEffectForType(Task):
    347 
    348     def OnEnter(self):
    349         self.SetStatusToSuccess()
    350         remote_repair_effect_registry = self.GetLastBlackboardValue(self.attributes.remoteRepairEffectRegistryAddress) or {}
    351         type_id = self.context.mySlimItem.typeID
    352         if type_id in remote_repair_effect_registry:
    353             return
    354         remote_repair_effect_id = get_remote_repair_effect_id(self)
    355         remote_repair_effect_registry[type_id] = remote_repair_effect_id
    356         self.SendBlackboardValue(self.attributes.remoteRepairEffectRegistryAddress, remote_repair_effect_registry)