Update Healer Man server

This commit is contained in:
phenom committed 2026-10-07 13:05:04 -04:00
1 parent 69809f7228
commit ff20ab8779
101 files changed
+16713 -945

No files matched your search

+62 -20
View File
@@ -1,3 +1,4 @@
import { healerManaRecoveryAmount } from "./healerManaRecovery";
import { nativeDisarmedMainHandAutoAttackProfile, nativeTrainingAutoAttackProfile } from "./romNativeCombatAmount";
import { romNativePlayerHitProfile } from "./romNativeCombatResolution";
import { classifyEnemyNpcSpellBlockPhase } from "./enemyBlocking";
@@ -8,7 +9,11 @@ import {
enemyDisarmAbilityWeaponChannel,
enemyDisarmBlocksAbilityCast,
enemyDisarmRequirementCandidates,
enemySpellHasMatchingEquippedItem,
} from "./enemyDisarmRuntime";
import { WOW335_HUNTER_DETERRENCE_ABILITY_ID, wow335DeterrenceAbilityContract,
wow335DeterrenceActivePackage } from "./wow335DeterrenceRuntime";
import { wow335HunterPreventionTypeForAbility } from "./wow335AbilityCatalog";
import type { EnemyDisarmWeaponChannel } from "./enemyWeaponDebuffs";
import {
abilitiesForClass,
@@ -36,6 +41,7 @@ import {
auraAdjustedCharacterStats,
effectiveActorAuras,
equipmentAdjustedCombatAmount,
refreshHealerManaRecovery,
refreshPriestPainAndSufferingDot,
stableCombatUnit,
useCombatStore,
@@ -189,7 +195,7 @@ import {
} from "./partyAbilityAi";
import { minimumHostileTargetsForAbility } from "./combatRotation";
import { emitRotationDecision } from "./rotationTelemetry";
import { imminentCooldownMechanic } from "./companionCooldownPolicy";
import { imminentCooldownMechanic, currentCompanionCooldownPolicy } from "./companionCooldownPolicy";
import { normalizeActorResourcePool, type DeathKnightRuneState } from "./combatActors";
import { enemyControlPreventsActions } from "./enemyAttacks";
import { enemyControlAbilityCanEscapeAuras } from "./enemyControlImmunityPurge";
@@ -2290,6 +2296,9 @@ function maybeProcPartyWarriorUnbridledWrath(
/** Weapon swings continue during the GCD and supply Rage for paid attacks. */
function advancePartyWeaponAttack(member: PartyMember, targetId: string, now: number): PartyMember {
if (useCombatStore.getState().auras.some((aura) => aura.targetId === member.id
&& (aura.expiresAt === null || aura.expiresAt > now)
&& aura.definition.utilityTags?.includes("pacified"))) return member;
const hasMaelstromWeaponTalent = member.classId === "shaman"
&& (member.talentRanks["talent-2057"] ?? 0) > 0;
if (partyUsesRangedAttacks(member) && baseClassFor(member.classId) !== "hunter"
@@ -2605,6 +2614,10 @@ export function partyAbilityTargetIsValid(
castCanMove = false,
): boolean {
const combat = useCombatStore.getState();
if (wow335HunterPreventionTypeForAbility(ability) === 2 && combat.auras.some((aura) => (
aura.targetId === member.id && (aura.expiresAt === null || aura.expiresAt > now)
&& aura.definition.utilityTags?.includes("pacified")
))) return false;
if (ability.wowWeaponRequirements) {
const loadout = resolvedEquipmentLoadout(combat.equipment, combat.inventory, {
ownerId: member.id,
@@ -2615,7 +2628,10 @@ export function partyAbilityTargetIsValid(
role: member.role,
level: member.level,
});
if (enemyDisarmBlocksAbilityCast(
const equippedItems = enemyDisarmRequirementCandidates(loadout);
if ((ability.id === WOW335_HUNTER_DETERRENCE_ABILITY_ID
&& !enemySpellHasMatchingEquippedItem(ability.wowWeaponRequirements, equippedItems))
|| enemyDisarmBlocksAbilityCast(
combat.auras,
member.id,
now,
@@ -2625,7 +2641,7 @@ export function partyAbilityTargetIsValid(
passive: ability.passive,
createsAura: ability.effects.some((effect) => effect.kind === "apply-aura"),
},
enemyDisarmRequirementCandidates(loadout),
equippedItems,
)) return false;
}
if (!coaWitchDoctorCanBrewIngredient(ability, combat.auras, member.id, member.talentRanks, now)) return false;
@@ -5192,6 +5208,7 @@ function membersDiffer(left: readonly PartyMember[], right: readonly PartyMember
* tank can redirect that tick's pressure before the death/respawn boundary.
*/
export function advanceDungeonPartyCombat(now = Date.now()): void {
refreshHealerManaRecovery(now);
const combat = useCombatStore.getState();
const party = usePartyStore.getState();
if (!party.members.length) return;
@@ -5321,6 +5338,10 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
manaSpiritRegenerationPerSecond: type === "mana" ? member.gearStats.spirit * 0.02 : 0,
}).current;
}
if (living && member.resourcePools.mana !== undefined && nativeManaMaximum > 0) {
resourcePools.mana = Math.min(nativeManaMaximum, (resourcePools.mana ?? 0)
+ healerManaRecoveryAmount(combat.auras, member.id, nativeManaMaximum, deltaSeconds));
}
const angerManagement = member.classId === "warrior"
? wow335WarriorAngerManagementProfile(
member.talentRanks[WOW335_WARRIOR_ANGER_MANAGEMENT_TALENT_ID] ?? 0,
@@ -5402,22 +5423,18 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
&& enemyControlPreventsActions(member.controlMechanic)
) {
const controlCombat = useCombatStore.getState();
const berserkerRageBase = member.classId === "warrior"
? abilitiesForCharacter(member.classId, member.secondaryClassId)
.find((ability) => ability.dbcSpellId === 18499)
: undefined;
const berserkerRage = berserkerRageBase
? learnedAbilityForBuild(berserkerRageBase, member.level, member.talentRanks)
: null;
const mechanic = member.controlMechanic;
if (berserkerRage && !member.activeCast && (mechanic === "fear" || mechanic === "stun")
&& enemyControlAbilityCanEscapeAuras(
berserkerRage, mechanic, controlCombat.auras, member.id, now,
)
&& partyAbilityTargetIsValid(member, berserkerRage, member.id, undefined, now)) {
const committed = commitPartyAbilityUseWithRogueCooldown(member, berserkerRage, now);
const escapeAbility = !member.activeCast && (mechanic === "fear" || mechanic === "stun" || mechanic === "confuse")
? abilitiesForCharacter(member.classId, member.secondaryClassId)
.map((ability) => learnedAbilityForBuild(ability, member.level, member.talentRanks))
.find((ability) => ability && enemyControlAbilityCanEscapeAuras(
ability, mechanic, controlCombat.auras, member.id, now,
) && partyAbilityTargetIsValid(member, ability, member.id, undefined, now))
: undefined;
if (escapeAbility) {
const committed = commitPartyAbilityUseWithRogueCooldown(member, escapeAbility, now);
if (committed) {
const execution = executePartyAbility(committed, berserkerRage, member.id, now, nextMembers);
const execution = executePartyAbility(committed, escapeAbility, member.id, now, nextMembers);
const partyStateAfterGrant = usePartyStore.getState().members.find((candidate) => candidate.id === member.id);
const actorAfterGrant = useCombatStore.getState().actors[member.id];
const controlledUntil = actorAfterGrant?.controlledUntil
@@ -5429,8 +5446,9 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
...execution.member,
controlledUntil,
controlMechanic: controlledUntil > now ? controlMechanic : null,
status: targetId ? member.role === "tank" ? "guarding" : "attacking" : "ready",
nextActionAt: now + Math.max(250, berserkerRage.castTimeMs, berserkerRage.gcdMs),
status: controlledUntil > now && enemyControlPreventsActions(controlMechanic)
? "holding" : targetId ? member.role === "tank" ? "guarding" : "attacking" : "ready",
nextActionAt: now + Math.max(250, escapeAbility.castTimeMs, escapeAbility.gcdMs),
};
continue;
}
@@ -5440,7 +5458,31 @@ export function advanceDungeonPartyCombat(now = Date.now()): void {
: { ...member, status: "holding" };
continue;
}
if (combat.auras.some((aura) => aura.targetId === member.id && aura.definition.utilityTags?.includes("pacified"))) {
const hunterDeterrence = member.classId === "hunter"
? abilitiesForCharacter(member.classId, member.secondaryClassId)
.map((ability) => learnedAbilityForBuild(ability, member.level, member.talentRanks))
.find((ability): ability is AbilityDefinition => Boolean(ability
&& ability.id === WOW335_HUNTER_DETERRENCE_ABILITY_ID && wow335DeterrenceAbilityContract(ability)))
: undefined;
const activeHunterDeterrence = hunterDeterrence && wow335DeterrenceActivePackage({
ability: hunterDeterrence, auras: combat.auras, defenderId: member.id, nowMs: now,
});
if (!member.activeCast && hunterDeterrence && !activeHunterDeterrence
&& Object.values(combat.mobs).some((mob) => !mob.dead && mob.engaged && mob.targetActorId === member.id
&& (member.health / Math.max(1, member.maxHealth)
< currentCompanionCooldownPolicy().generic.defensiveHealthPercent / 100
|| Boolean(imminentCooldownMechanic(mob, member.role, now))))
&& partyAbilityIsReady(member, hunterDeterrence, now)
&& partyAbilityTargetIsValid(member, hunterDeterrence, member.id, undefined, now)) {
const committed = commitPartyAbilityUseWithRogueCooldown(member, hunterDeterrence, now);
if (committed) {
const execution = executePartyAbility(committed, hunterDeterrence, member.id, now, nextMembers);
nextMembers[index] = { ...execution.member, status: "holding", nextActionAt: now + 250 };
continue;
}
}
if (!activeHunterDeterrence && combat.auras.some((aura) => aura.targetId === member.id
&& (aura.expiresAt === null || aura.expiresAt > now) && aura.definition.utilityTags?.includes("pacified"))) {
nextMembers[index] = member.status === "holding" ? member : { ...member, status: "holding" };
continue;
}