Release Healer Man 0.1.54
This commit is contained in:
1 parent
d6976e6ef6
commit
2dbb0acfc1
26 files changed
+1893
-912
No files matched your search
@@ -745,9 +745,16 @@ function partyAbilityTargetIsValid(
|
||||
&& !ability.effects.some((effect) => effect.kind === "resurrection")) return false;
|
||||
const distance = Math.sqrt(distanceSquared(origin, targetPosition));
|
||||
const targeting = targetingRulesForAbility(ability);
|
||||
const maximumRange = hostile && ability.target === "self"
|
||||
const catalogMaximumRange = hostile && ability.target === "self"
|
||||
? targeting.radius ?? ability.radius ?? ability.range.max
|
||||
: ability.range.max;
|
||||
// Several stock melee SkillLine rows use a three-yard DBC range while the
|
||||
// companion formation deliberately settles just outside that distance.
|
||||
// Apply the same combat-reach leeway used by companion weapon swings so
|
||||
// builders, diseases, bleeds, and finishers remain usable in formation.
|
||||
const maximumRange = hostile && catalogMaximumRange > 0 && catalogMaximumRange <= 5
|
||||
? Math.max(catalogMaximumRange, partyAttackRange(member))
|
||||
: catalogMaximumRange;
|
||||
if (distance < ability.range.min || distance > maximumRange || Math.abs(origin[1] - targetPosition[1]) > 8) return false;
|
||||
if (targeting.requiresLineOfSight && !combatHasLineOfSight(origin, targetPosition)) return false;
|
||||
if (hostile && targeting.requiresFacing) {
|
||||
@@ -905,6 +912,45 @@ function choosePartySupportIntent(
|
||||
return { ability, targetId: dispelTargetId };
|
||||
}
|
||||
}
|
||||
// Preventive damage-taken procs (Earth Shield / Prayer of Mending-shaped
|
||||
// effects) belong on the tank before damage lands. Normal HoTs and direct
|
||||
// heals still wait for the injured-target path below.
|
||||
const preventiveTarget = members
|
||||
.filter((candidate) => candidate.health > 0)
|
||||
.sort((left, right) => (
|
||||
Number(right.role === "tank") - Number(left.role === "tank")
|
||||
|| left.id.localeCompare(right.id)
|
||||
))[0];
|
||||
if (preventiveTarget) {
|
||||
const maintainedAbilityIds = new Set(combat.auras
|
||||
.filter((aura) => aura.sourceId === member.id && (aura.expiresAt === null || aura.expiresAt > now))
|
||||
.flatMap((aura) => abilitiesForCharacter(member.classId, member.secondaryClassId)
|
||||
.map((ability) => learnedAbilityForBuild(ability, member.level, member.talentRanks))
|
||||
.filter((ability): ability is AbilityDefinition => ability !== null)
|
||||
.filter((ability) => ability.effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.id === aura.definition.id))
|
||||
.map((ability) => ability.id)));
|
||||
const activeAuraExclusiveGroups = new Set(combat.auras
|
||||
.filter((aura) => aura.sourceId === member.id && (aura.expiresAt === null || aura.expiresAt > now))
|
||||
.flatMap((aura) => aura.definition.exclusiveGroup ? [aura.definition.exclusiveGroup] : []));
|
||||
const preventive = choosePartyAbility(member, {
|
||||
now,
|
||||
target: null,
|
||||
friendlyTargets: [{
|
||||
id: preventiveTarget.id,
|
||||
health: preventiveTarget.health,
|
||||
maxHealth: preventiveTarget.maxHealth,
|
||||
}],
|
||||
maintainedAbilityIds,
|
||||
activeAuraExclusiveGroups,
|
||||
canUseAbility: ability => (ability.target === "friendly" || ability.target === "any")
|
||||
&& ability.effects.some((effect) => effect.kind === "apply-aura"
|
||||
&& effect.aura.disposition === "buff"
|
||||
&& effect.aura.procs?.some((proc) => proc.triggers === "damage-taken"))
|
||||
&& partyAbilityTargetIsValid(member, ability, preventiveTarget.id),
|
||||
});
|
||||
if (preventive) return { ability: preventive, targetId: preventiveTarget.id };
|
||||
}
|
||||
const injured = [
|
||||
...(!simulatedPartyExcludesPlayer() && combat.health > 0 && combat.health < combat.maxHealth
|
||||
? [{ id: PLAYER_AGGRO_ID, health: combat.health, maxHealth: combat.maxHealth, role: "healer" as const }]
|
||||
@@ -1089,6 +1135,23 @@ function executePartyAbility(
|
||||
ability.id,
|
||||
now,
|
||||
);
|
||||
// Resource restoration writes through the authoritative actor store.
|
||||
// Mirror only its resource fields: the actor has not received this
|
||||
// action's cooldown/GCD yet, so a full actor-to-member copy would erase
|
||||
// the committed action state and let generators such as Bloodrage spam.
|
||||
if (friendlyTargetId === member.id) {
|
||||
const resourceMirror = memberFromCompanionActor(
|
||||
runtimeMember,
|
||||
useCombatStore.getState().actors[member.id],
|
||||
);
|
||||
runtimeMember = {
|
||||
...runtimeMember,
|
||||
resource: resourceMirror.resource,
|
||||
maxResource: resourceMirror.maxResource,
|
||||
resourcePools: resourceMirror.resourcePools,
|
||||
lastResourceSpentAt: resourceMirror.lastResourceSpentAt,
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if (effect.kind === "combo") {
|
||||
const current = runtimeMember.comboTargetId === targetId ? runtimeMember.comboPoints : 0;
|
||||
|
||||
Reference in new issue
Block a user