Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Py4GWCoreLib/BuildMgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ def ResolveAllyTarget(
TargetLowestAllyMartial,
TargetLowestAllyMelee,
TargetLowestAllyRanged,
TargetSplinterWeapon,
TargetAllyNonEnchanted,
TargetAllyNonWeaponSpelled,
TargetMinionNonEnchanted,
Expand Down Expand Up @@ -455,6 +456,8 @@ def ResolveAllyTarget(
return TargetAllyNonEnchanted(**range_kwargs)
if target_allegiance == Skilltarget.NonWeaponSpelledAlly.value:
return Player.GetAgentID() if TargetAllyNonWeaponSpelled(**range_kwargs) else 0
if target_allegiance == Skilltarget.SplinterWeapon.value:
return TargetSplinterWeapon(distance=ally_distance)

if target_allegiance in (
Skilltarget.Ally.value,
Expand Down
4 changes: 3 additions & 1 deletion Py4GWCoreLib/HeroAI/combat.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from Py4GWCoreLib.GlobalCache.HexRemovalPriority import get_hexed_ally_for_removal
from Py4GWCoreLib.EnemyBlacklist import EnemyBlacklist
from .custom_skill import CustomSkillClass
from .targeting import TargetLowestAlly, TargetLowestAllyEnergy, TargetClusteredEnemy, TargetLowestAllyCaster, TargetLowestAllyMartial, TargetLowestAllyMelee, TargetLowestAllyRanged, GetAllAlliesArray, TargetAllyWeaponSpell, TargetMinionOrAllyNonEnchanted, TargetMinionNonEnchanted, TargetAllyNonEnchanted, TargetAllyNonWeaponSpelled, TargetDeadPartyMember, IsResurrectablePartyMember
from .targeting import TargetLowestAlly, TargetLowestAllyEnergy, TargetClusteredEnemy, TargetLowestAllyCaster, TargetLowestAllyMartial, TargetLowestAllyMelee, TargetLowestAllyRanged, TargetSplinterWeapon, GetAllAlliesArray, TargetAllyWeaponSpell, TargetMinionOrAllyNonEnchanted, TargetMinionNonEnchanted, TargetAllyNonEnchanted, TargetAllyNonWeaponSpelled, TargetDeadPartyMember, IsResurrectablePartyMember
from .targeting import GetEnemyAttacking, GetEnemyCasting, GetEnemyCastingSpell, GetEnemyCastingSpellOrChant, GetEnemyInjured, GetEnemyConditioned, GetEnemyHealthy
from .targeting import GetEnemyHexed, GetEnemyDegenHexed, GetEnemyEnchanted, GetEnemyMoving, GetEnemyKnockedDown
from .targeting import GetEnemyBleeding, GetEnemyPoisoned, GetEnemyCrippled
Expand Down Expand Up @@ -937,6 +937,8 @@ def get_lowest_ally() -> int:
v_target = TargetLowestAllyRanged(filter_skill_id=self.skills[slot].skill_id)
if v_target == 0 and not targeting_strict:
v_target = get_lowest_ally()
elif target_allegiance == Skilltarget.SplinterWeapon:
v_target = TargetSplinterWeapon()
elif target_allegiance == Skilltarget.OtherAlly:
if self.skills[slot].custom_skill_data.Nature == SkillNature.EnergyBuff.value:
v_target = TargetLowestAllyEnergy(other_ally=True, filter_skill_id=self.skills[slot].skill_id, less_energy=self.skills[slot].custom_skill_data.Conditions.LessEnergy)
Expand Down
2 changes: 1 addition & 1 deletion Py4GWCoreLib/HeroAI/custom_skill_src/ritualist.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ def __init__(self, skill_data):
skill = CustomSkill()
skill.SkillID = GLOBAL_CACHE.Skill.GetID("Splinter_Weapon")
skill.SkillType = SkillType.WeaponSpell.value
skill.TargetAllegiance = Skilltarget.AllyMartial.value
skill.TargetAllegiance = Skilltarget.SplinterWeapon.value
skill.Nature = SkillNature.Buff.value
skill_data[skill.SkillID] = skill

Expand Down
65 changes: 65 additions & 0 deletions Py4GWCoreLib/HeroAI/targeting.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,71 @@ def TargetLowestAlly(other_ally=False, filter_skill_id=0, distance=Range.Spellca
return Utils.GetFirstFromArray(ally_array)


def TargetSplinterWeapon(distance=Range.Spellcast.value):
"""Resolve Splinter Weapon's ordered martial/summon target policy.

Party members deliberately outrank temporary allies, matching the game's
current AI priority. EVAS is the first non-party fallback because its
dagger chain reliably spends Splinter's limited attack charges; minions
are the final fallback. Candidates carrying any weapon spell are skipped
here so selection can continue into the next priority tier.
"""
ebon_vanguard_assassin_model_id = 5903
player_id = Player.GetAgentID()
player_xy = Player.GetXY()

def is_eligible(agent_id: int) -> bool:
return bool(
agent_id
and Agent.IsValid(agent_id)
and Agent.IsTargettable(agent_id)
and Agent.IsAlive(agent_id)
and Utils.Distance(Agent.GetXY(agent_id), player_xy) <= distance
and not Routines.Checks.Agents.IsWeaponSpelled(agent_id)
)

if is_eligible(player_id) and Routines.Checks.Agents.IsMartial(player_id):
return player_id

party_candidates = [
agent_id
for agent_id in GetAllAlliesArray(distance, ordered=True) or []
if agent_id != player_id
and is_eligible(agent_id)
and Routines.Party.IsPartyMember(agent_id)
and Routines.Checks.Agents.IsMartial(agent_id)
]
if party_candidates:
return party_candidates[0]

allied_creatures = set(AgentArray.GetAllyArray() or [])
allied_creatures.update(AgentArray.GetNPCMinipetArray() or [])
evas_candidates = [
agent_id
for agent_id in allied_creatures
if is_eligible(agent_id)
and Agent.GetModelID(agent_id) == ebon_vanguard_assassin_model_id
]
if evas_candidates:
return min(
evas_candidates,
key=lambda agent_id: (Utils.Distance(Agent.GetXY(agent_id), player_xy), agent_id),
)

minion_candidates = [
agent_id
for agent_id in AgentArray.GetMinionArray() or []
if is_eligible(agent_id)
]
if minion_candidates:
return min(
minion_candidates,
key=lambda agent_id: (Utils.Distance(Agent.GetXY(agent_id), player_xy), agent_id),
)

return 0


def TargetMinionOrAllyNonEnchanted(filter_skill_id=0, distance=Range.Spellcast.value):
minion_array = AgentArray.GetMinionArray()
minion_array = AgentArray.Filter.ByDistance(minion_array, Player.GetXY(), distance)
Expand Down
1 change: 1 addition & 0 deletions Py4GWCoreLib/HeroAI/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class Skilltarget (IntEnum):
ResurrectionAlly = 40
HexedAlly = 41
EnemyNotNearby = 42
SplinterWeapon = 44



Expand Down
6 changes: 5 additions & 1 deletion Py4GWCoreLib/SkillManager.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from Py4GWCoreLib.HeroAI.custom_skill import CustomSkillClass
from Py4GWCoreLib.HeroAI.types import SkillType,SkillNature, Skilltarget

from Py4GWCoreLib.HeroAI.targeting import TargetLowestAlly, TargetLowestAllyEnergy, TargetClusteredEnemy, TargetLowestAllyCaster, TargetLowestAllyMartial, TargetLowestAllyMelee, TargetLowestAllyRanged, GetAllAlliesArray
from Py4GWCoreLib.HeroAI.targeting import TargetLowestAlly, TargetLowestAllyEnergy, TargetClusteredEnemy, TargetLowestAllyCaster, TargetLowestAllyMartial, TargetLowestAllyMelee, TargetLowestAllyRanged, TargetSplinterWeapon, GetAllAlliesArray
from Py4GWCoreLib.HeroAI.targeting import TargetMinionOrAllyNonEnchanted, TargetMinionNonEnchanted, TargetAllyNonEnchanted, TargetAllyNonWeaponSpelled
from Py4GWCoreLib.HeroAI.targeting import GetEnemyAttacking, GetEnemyCasting, GetEnemyCastingSpell, GetEnemyInjured, GetEnemyConditioned, GetEnemyHealthy
from Py4GWCoreLib.HeroAI.targeting import GetEnemyHexed, GetEnemyDegenHexed, GetEnemyEnchanted, GetEnemyMoving, GetEnemyKnockedDown
Expand Down Expand Up @@ -457,6 +457,8 @@ def _TargetLowestAlly(other_ally=False, filter_skill_id=0):
v_target = _TargetLowestAllyRanged(filter_skill_id=skills[slot].skill_id)
if v_target == 0 and not targeting_strict:
v_target = lowest_ally
elif target_allegiance == Skilltarget.SplinterWeapon:
v_target = TargetSplinterWeapon()
elif target_allegiance == Skilltarget.OtherAlly:
if skills[slot].custom_skill_data.Nature == SkillNature.EnergyBuff.value:
v_target = _TargetLowestAllyEnergy(other_ally=True, filter_skill_id=skills[slot].skill_id)
Expand Down Expand Up @@ -1563,6 +1565,8 @@ def get_lowest_ally():
v_target = Routines.Targeting.TargetLowestAllyRanged(filter_skill_id=self.skills[slot].skill_id)
if v_target == 0 and not targeting_strict:
v_target = get_lowest_ally()
elif target_allegiance == Skilltarget.SplinterWeapon:
v_target = TargetSplinterWeapon()
elif target_allegiance == Skilltarget.OtherAlly:
if self.skills[slot].custom_skill_data.Nature == SkillNature.EnergyBuff.value:
v_target = Routines.Targeting.TargetLowestAllyEnergy(other_ally=True, filter_skill_id=self.skills[slot].skill_id)
Expand Down