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

group.py (13921B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\packages\behaviors\actions\group.py
      2 from collections import defaultdict
      3 import uthread2
      4 from behaviors.blackboards import BlackboardDeletedError
      5 from behaviors.const.behaviorroles import COMBAT_ROLES
      6 from behaviors.const.combat import COMBAT_MAX_EFFECTIVE_FALLOFF_DISTANCE_FACTOR
      7 from behaviors.const.combat import COMBAT_MIN_EFFECTIVE_COMBAT_RANGE_FACTOR
      8 from behaviors.const.loot import EXTRA_LOOT_TABLES_BY_OWNER_ID_AND_ENTITY_GROUP_ID
      9 from behaviors.groups.mixin import GroupTaskMixin
     10 from behaviors.tasks import Task
     11 from behaviors.trees.combat import ROLE_ADDRESS, COMBAT_ORBIT_RANGE, COMBAT_WARP_AT_DISTANCE
     12 from behaviors.utility.ballparks import get_ship_balls_in_bubble, get_distance_between, get_ball, is_target_valid, get_slim_item, is_ball_cloaked
     13 from behaviors.utility.dogmatic import type_has_effect, get_entity_max_missile_range
     14 from behaviors.utility.inventory import get_type_id_by_item_id
     15 from ccpProfile import TimedFunction
     16 from dogma.const import effectMissileLaunchingForEntity, effectTargetAttack
     17 from dogma.effects.restricted.util import get_effect_range_with_falloff_multiplier
     18 from lootutils.grouploot import AssignAdditionalLootToGroupEntities
     19 from random import choice
     20 import logging
     21 logger = logging.getLogger(__name__)
     22 UNSPAWN_GROUP_SLEEP_DELAY_SEC = 0.2
     23 
     24 class UpdateMaxMemberCount(Task, GroupTaskMixin):
     25 
     26     @TimedFunction('behaviors::actions::group::UpdateMaxMemberCount::OnEnter')
     27     def OnEnter(self):
     28         current_count = self.GetEntityGroup().Count()
     29         last_count = self.GetLastBlackboardValue(self.attributes.maxMemberCountAddress)
     30         if last_count is None:
     31             self.SendBlackboardValue(self.attributes.maxMemberCountAddress, current_count)
     32         elif current_count > last_count:
     33             self.SendBlackboardValue(self.attributes.maxMemberCountAddress, current_count)
     34         self.SetStatusToSuccess()
     35 
     36 
     37 class UpdateCurrentMemberCount(Task, GroupTaskMixin):
     38 
     39     @TimedFunction('behaviors::actions::group::UpdateCurrentMemberCount::OnEnter')
     40     def OnEnter(self):
     41         current_count = self.GetEntityGroup().Count()
     42         last_count = self.GetLastBlackboardValue(self.attributes.currentMemberCountAddress)
     43         if current_count != last_count:
     44             self.SendBlackboardValue(self.attributes.currentMemberCountAddress, current_count)
     45         self.SetStatusToSuccess()
     46 
     47 
     48 class ChooseCommander(Task, GroupTaskMixin):
     49 
     50     @TimedFunction('behaviors::actions::group::ChooseCommander::OnEnter')
     51     def OnEnter(self):
     52         self.SetStatusToSuccess()
     53         requiredRole = getattr(self.attributes, 'requiredCommanderRole', None)
     54         existingCommanderItemId = self.GetLastBlackboardValue(self.attributes.commanderAddress)
     55         chosenGroupMemberId = self._ChooseCommanderFromGroup(requiredRole, existingCommanderItemId)
     56         if chosenGroupMemberId == existingCommanderItemId:
     57             return
     58         self.SendBlackboardValue(self.attributes.commanderAddress, chosenGroupMemberId)
     59         if chosenGroupMemberId:
     60             self._ResetCommanderNavigationRangesAndBehaviorTree(chosenGroupMemberId)
     61 
     62     def _ChooseCommanderFromGroup(self, requiredRole, existingCommanderItemId):
     63         membersWithCombatRoles = self._GetMembersWithCombatRoles()
     64         self._RemoveInvalidCommanderRoles(membersWithCombatRoles)
     65         if not membersWithCombatRoles:
     66             return None
     67         if self._IsCurrentCommanderValid(requiredRole, existingCommanderItemId, membersWithCombatRoles):
     68             return existingCommanderItemId
     69         if requiredRole and requiredRole in membersWithCombatRoles:
     70             return choice(membersWithCombatRoles[requiredRole])
     71         randomRole = choice(membersWithCombatRoles.keys())
     72         return choice(membersWithCombatRoles[randomRole])
     73 
     74     def _RemoveInvalidCommanderRoles(self, membersWithCombatRoles):
     75         for invalidRole in self.attributes.invalidCommanderRoles:
     76             if invalidRole in membersWithCombatRoles:
     77                 del membersWithCombatRoles[invalidRole]
     78 
     79     def _IsCurrentCommanderValid(self, requiredRole, existingCommanderItemId, membersWithCombatRoles):
     80         if not existingCommanderItemId or not self.context.ballpark.HasBall(existingCommanderItemId):
     81             return False
     82         if not self._IsCurrentCommanderRoleValid(requiredRole, existingCommanderItemId, membersWithCombatRoles):
     83             return False
     84         return True
     85 
     86     def _IsCurrentCommanderRoleValid(self, requiredRole, existingCommanderItemId, membersWithCombatRoles):
     87         if not requiredRole or requiredRole not in membersWithCombatRoles:
     88             return True
     89         try:
     90             commandersRole = self._GetRoleByGroupMemberId(existingCommanderItemId)
     91             if commandersRole == requiredRole:
     92                 return True
     93         except BlackboardDeletedError:
     94             return False
     95 
     96         return False
     97 
     98     def _GetRoleByGroupMemberId(self, groupMemberId):
     99         _, channelAddressName = ROLE_ADDRESS
    100         return self.GetMessageChannelForItemId(groupMemberId, channelAddressName).GetLastMessageValue()
    101 
    102     def _GetMembersWithCombatRoles(self):
    103         membersWithCombatRoles = defaultdict(list)
    104         memberIds = self.GetMemberIdList()
    105         for groupMemberId in memberIds:
    106             try:
    107                 memberRole = self._GetRoleByGroupMemberId(groupMemberId)
    108                 if memberRole in COMBAT_ROLES:
    109                     membersWithCombatRoles[memberRole].append(groupMemberId)
    110             except BlackboardDeletedError:
    111                 continue
    112 
    113         return membersWithCombatRoles
    114 
    115     def _ResetCommanderNavigationRangesAndBehaviorTree(self, chosenGroupMemberId):
    116         _, combatOrbitAddress = COMBAT_ORBIT_RANGE
    117         _, combatWarpAtDistanceAddress = COMBAT_WARP_AT_DISTANCE
    118         self.GetMessageChannelForItemId(chosenGroupMemberId, combatOrbitAddress).SendMessage(None)
    119         self.GetMessageChannelForItemId(chosenGroupMemberId, combatWarpAtDistanceAddress).SendMessage(None)
    120         commanderBehaviorTree = self.context.entityLocation.GetBehaviorTreeManager().GetEntryByKey(chosenGroupMemberId)
    121         commanderBehaviorTree.ResetTree()
    122 
    123 
    124 class GetMemberList(Task, GroupTaskMixin):
    125 
    126     @TimedFunction('behaviors::actions::group::GetMemberList::OnEnter')
    127     def OnEnter(self):
    128         member_id_list = self.GetMemberIdList()
    129         self.SendBlackboardValue(self.attributes.memberListAddress, member_id_list)
    130         self.SetStatusToSuccess()
    131 
    132 
    133 class AssignExtraLootTableByOwnerIdAndEntityGroupId(Task, GroupTaskMixin):
    134 
    135     @TimedFunction('behaviors::actions::group::AssignExtraLootTableByOwnerIdAndEntityGroupId::OnEnter')
    136     def OnEnter(self):
    137         self.SetStatusToSuccess()
    138         newExtraLoot = self._GetExtraLootDataByOwnerId()
    139         if not newExtraLoot:
    140             return
    141         extraLoot = self.GetLastBlackboardValue(self.attributes.additionalLootTablesAddress) or defaultdict(set)
    142         for entityGroupId, lootTableIds in newExtraLoot.iteritems():
    143             extraLoot[entityGroupId].update(lootTableIds)
    144 
    145         self.SendBlackboardValue(self.attributes.additionalLootTablesAddress, extraLoot)
    146         AssignAdditionalLootToGroupEntities(self.context.entityLocation, self.context.myEntityGroupId)
    147 
    148     def _GetExtraLootDataByOwnerId(self):
    149         return EXTRA_LOOT_TABLES_BY_OWNER_ID_AND_ENTITY_GROUP_ID.get(self.GetGroupOwnerId())
    150 
    151 
    152 class UnspawnGroup(Task, GroupTaskMixin):
    153 
    154     @TimedFunction('behaviors::actions::group::UnspawnGroup::OnEnter')
    155     def OnEnter(self):
    156         self.SetStatusToSuccess()
    157         uthread2.StartTasklet(self._UnspawnGroup)
    158 
    159     def _UnspawnGroup(self):
    160         uthread2.sleep(UNSPAWN_GROUP_SLEEP_DELAY_SEC)
    161         self.context.entityLocation.UnspawnBehaviorEntityGroupWithRespawnEnabled(self.context.myEntityGroupId)
    162 
    163 
    164 class PostWeaponRangesForGroup(Task, GroupTaskMixin):
    165 
    166     @TimedFunction('behaviors::actions::group::PostWeaponRangesForGroup::OnEnter')
    167     def OnEnter(self):
    168         self.SetStatusToSuccess()
    169         max_effective_weapon_range = self._get_max_effective_weapon_range()
    170         self.SendBlackboardValue(self.attributes.maxEffectiveWeaponRangeAddress, max_effective_weapon_range)
    171         min_effective_weapon_range = max_effective_weapon_range * COMBAT_MIN_EFFECTIVE_COMBAT_RANGE_FACTOR
    172         self.SendBlackboardValue(self.attributes.minEffectiveWeaponRangeAddress, min_effective_weapon_range)
    173 
    174     def _get_max_effective_weapon_range(self):
    175         max_effective_weapon_range_for_group = []
    176         entities_to_consider = self._get_entities_to_consider()
    177         if not entities_to_consider:
    178             return 0
    179         for entity_id in entities_to_consider:
    180             entity_type_id = get_type_id_by_item_id(self, entity_id)
    181             if entity_type_id is None:
    182                 continue
    183             max_effective_weapon_range_for_entity = self._get_max_effective_weapon_range_for_entity(entity_id, entity_type_id)
    184             if max_effective_weapon_range_for_entity == 0:
    185                 continue
    186             max_effective_weapon_range_for_group.append(max_effective_weapon_range_for_entity)
    187             logger.debug('Behavior: max effective weaponRange for entityType=%s set to=%s', entity_type_id, max_effective_weapon_range_for_entity)
    188 
    189         if not max_effective_weapon_range_for_group:
    190             return 0
    191         max_effective_weapon_range_for_group.sort()
    192         return max_effective_weapon_range_for_group[len(max_effective_weapon_range_for_group) / 2]
    193 
    194     def _get_entities_to_consider(self):
    195         roles_to_consider = self._get_roles_to_consider()
    196         if not roles_to_consider:
    197             return self.GetMemberIdList()
    198         entities_by_role = self._get_entities_by_role()
    199         entities_to_consider = []
    200         if entities_by_role is None:
    201             logger.debug('Behavior: no entities by role posted yet for PostWeaponRangesForGroup, most likely pending initial spawns')
    202             return entities_to_consider
    203         for role in roles_to_consider:
    204             if role in entities_by_role:
    205                 entities_to_consider.extend(entities_by_role[role])
    206 
    207         return entities_to_consider
    208 
    209     def _get_roles_to_consider(self):
    210         return self.attributes.rolesToConsider
    211 
    212     def _get_entities_by_role(self):
    213         return self.GetLastBlackboardValue(self.attributes.entitiesByRole)
    214 
    215     def _get_max_effective_weapon_range_for_entity(self, entity_id, entity_type_id):
    216         turret_range = self._get_max_effective_turret_range_for_entity(entity_id, entity_type_id)
    217         missile_range = self._get_max_effective_missile_range_for_entity(entity_type_id)
    218         if turret_range == 0 and missile_range == 0:
    219             return 0
    220         if missile_range == 0 or turret_range < missile_range:
    221             return turret_range
    222         return missile_range
    223 
    224     def _get_max_effective_turret_range_for_entity(self, entity_id, entity_type_id):
    225         if not type_has_effect(self, entity_type_id, effectTargetAttack):
    226             return 0
    227         return get_effect_range_with_falloff_multiplier(self.context.dogmaLM, effectTargetAttack, entity_id, COMBAT_MAX_EFFECTIVE_FALLOFF_DISTANCE_FACTOR)
    228 
    229     def _get_max_effective_missile_range_for_entity(self, entity_type_id):
    230         if not type_has_effect(self, entity_type_id, effectMissileLaunchingForEntity):
    231             return 0
    232         return get_entity_max_missile_range(self, entity_type_id)
    233 
    234 
    235 class GetClosestPlayerBallToGroupLeader(Task, GroupTaskMixin):
    236 
    237     def OnEnter(self):
    238         self.SetStatusToSuccess()
    239         closest_player_ball_id = self._get_closest_player_ball_id_to_group()
    240         self.SendBlackboardValue(self.attributes.ballIdAddress, closest_player_ball_id)
    241 
    242     def _get_closest_player_ball_id_to_group(self):
    243         selected_group_member_id = self._select_group_member()
    244         bubble_id = get_ball(self, selected_group_member_id).newBubbleId
    245         return self._get_closest_player_to_member(selected_group_member_id, bubble_id)
    246 
    247     def _get_closest_player_to_member(self, selected_group_member_id, bubble_id):
    248         closest_player_ball_id = None
    249         shortest_distance = None
    250         for player_ship_ball_id in get_ship_balls_in_bubble(self, bubble_id):
    251             if not is_target_valid(self, player_ship_ball_id):
    252                 continue
    253             distance = get_distance_between(self, selected_group_member_id, player_ship_ball_id)
    254             if shortest_distance is None or distance < shortest_distance:
    255                 closest_player_ball_id = player_ship_ball_id
    256                 shortest_distance = distance
    257 
    258         return closest_player_ball_id
    259 
    260     def _select_group_member(self):
    261         if not self.HasAttribute('commanderAddress'):
    262             return self.GetMemberIdList()[0]
    263         commander_id = self.GetLastBlackboardValue(self.attributes.commanderAddress)
    264         return commander_id or self.GetMemberIdList()[0]
    265 
    266 
    267 class IsAnyPlayerShipOnGridWithGroupMember(Task, GroupTaskMixin):
    268 
    269     def OnEnter(self):
    270         self.SetStatusToSuccess()
    271         bubbles = self.GetMemberBubbleSet()
    272         for bubble_id in bubbles:
    273             if self._bubble_contains_player(bubble_id):
    274                 self.SendBlackboardValue(self.attributes.resultAddress, True)
    275                 return
    276 
    277         self.SendBlackboardValue(self.attributes.resultAddress, False)
    278 
    279     def _is_target_valid(self, target_id):
    280         if target_id is None:
    281             return False
    282         slim_item = get_slim_item(self, target_id)
    283         if slim_item is None:
    284             return False
    285         if not self.attributes.includeCloaked and is_ball_cloaked(self, target_id):
    286             return False
    287         return True
    288 
    289     def _bubble_contains_player(self, bubble_id):
    290         for player_ship_ball_id in get_ship_balls_in_bubble(self, bubble_id):
    291             if self._is_target_valid(player_ship_ball_id):
    292                 return True
    293 
    294         return False