forked from phenom/healer-man
122 lines
5.0 KiB
TypeScript
122 lines
5.0 KiB
TypeScript
import { WOW335_TALENT_NODES } from "./wow335Talents.generated";
|
|
import generatedData from "./wow335AbilityData.generated.json";
|
|
|
|
export const WOW335_WARRIOR_ANGER_MANAGEMENT_TALENT_ID = "talent-137";
|
|
export const WOW335_WARRIOR_ANGER_MANAGEMENT_CHARGE_AURA_TAG = "wow335-warrior-anger-management-charge";
|
|
|
|
const SOURCE = WOW335_TALENT_NODES.find((node) => (
|
|
node.id === WOW335_WARRIOR_ANGER_MANAGEMENT_TALENT_ID
|
|
));
|
|
const CHARGE_SOURCE = generatedData.classes.warrior.find((ability) => ability.name === "Charge");
|
|
|
|
export function wow335WarriorChargeRank(spellId: number): number {
|
|
return CHARGE_SOURCE?.ranks.find((rank) => rank.spellId === spellId)?.rank ?? 0;
|
|
}
|
|
|
|
export interface Wow335WarriorAngerManagementProfile {
|
|
readonly rank: number;
|
|
readonly ragePerTick: number;
|
|
readonly tickIntervalMs: number;
|
|
readonly chargeBonusRage: number;
|
|
readonly chargeDamageReductionPercent: number;
|
|
readonly chargeDamageReductionDurationMs: number;
|
|
readonly chargeDamageReductionSpellId: number | null;
|
|
}
|
|
|
|
export interface Wow335WarriorChargeRageProfile {
|
|
readonly chargeRank: number;
|
|
readonly chargeSpellId: number;
|
|
readonly angerManagementRank: number;
|
|
readonly baseRage: number;
|
|
readonly angerManagementBonusRage: number;
|
|
readonly totalRage: number;
|
|
}
|
|
|
|
/**
|
|
* Resolves the base Charge Rage and the selected Anger Management rank.
|
|
* Generated Charge tooltips hide the amount as "listed amount" while the
|
|
* local dummy effect contains 90/120/150. WotLKDB's Charge rank-1 tooltip
|
|
* confirms that dummy value 90 means 9 Rage; the same local field conversion
|
|
* is applied to ranks 2 and 3 (120/150 => 12/15 Rage). The database supplies
|
|
* only the missing unit scale; local rank rows remain the data source.
|
|
*/
|
|
export function wow335WarriorChargeRageProfile(
|
|
chargeRank: number,
|
|
angerManagementRank: number,
|
|
): Wow335WarriorChargeRageProfile | null {
|
|
const maxChargeRank = CHARGE_SOURCE?.ranks.length ?? 0;
|
|
const safeChargeRank = Math.min(maxChargeRank, Math.max(0, Math.trunc(
|
|
Number.isFinite(chargeRank) ? chargeRank : 0,
|
|
)));
|
|
const charge = CHARGE_SOURCE?.ranks.find((rank) => rank.rank === safeChargeRank);
|
|
const rageEffect = charge?.effects.find((effect) => effect.effectType === 3 && effect.implicitTargetA === 1);
|
|
if (safeChargeRank === 0 || !charge || !rageEffect || rageEffect.basePoints <= 0 || rageEffect.basePoints % 10 !== 0) {
|
|
return null;
|
|
}
|
|
|
|
const maxTalentRank = SOURCE?.rankSpellIds.length ?? 0;
|
|
const safeTalentRank = Math.min(maxTalentRank, Math.max(0, Math.trunc(
|
|
Number.isFinite(angerManagementRank) ? angerManagementRank : 0,
|
|
)));
|
|
const talentProfile = wow335WarriorAngerManagementProfile(safeTalentRank);
|
|
const baseRage = rageEffect.basePoints / 10;
|
|
const angerManagementBonusRage = talentProfile?.chargeBonusRage ?? 0;
|
|
return {
|
|
chargeRank: safeChargeRank,
|
|
chargeSpellId: charge.spellId,
|
|
angerManagementRank: safeTalentRank,
|
|
baseRage,
|
|
angerManagementBonusRage,
|
|
totalRage: baseRage + angerManagementBonusRage,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Reads the local generated rank tooltip and rank effects. The three rank
|
|
* tooltips specify passive Rage and Charge bonuses; the maximum-rank tooltip
|
|
* additionally describes Charge's four-second damage reduction. Rank 1 and
|
|
* 2 mention that capstone in their text only as a rank-3 unlock, so it is not
|
|
* exposed until the final rank is allocated.
|
|
*/
|
|
export function wow335WarriorAngerManagementProfile(
|
|
rank: number,
|
|
): Wow335WarriorAngerManagementProfile | null {
|
|
const rankCount = SOURCE?.rankSpellIds.length ?? 0;
|
|
const safeRank = Math.min(rankCount, Math.max(0, Math.trunc(Number.isFinite(rank) ? rank : 0)));
|
|
const description = SOURCE?.rankDescriptions[safeRank - 1];
|
|
const rankEffect = SOURCE?.rankEffects[safeRank - 1];
|
|
if (safeRank === 0 || !description || !rankEffect) return null;
|
|
|
|
const rage = description.match(/generates (\d+) rage per (\d+) seconds/i);
|
|
const chargeBonus = description.match(/Charge ability by (\d+)/i);
|
|
if (!rage || !chargeBonus) return null;
|
|
|
|
const ragePerTick = Number(rage[1]);
|
|
const tickIntervalMs = Number(rage[2]) * 1_000;
|
|
const chargeBonusRage = Number(chargeBonus[1]);
|
|
if (![ragePerTick, tickIntervalMs, chargeBonusRage].every(Number.isFinite)
|
|
|| ragePerTick < 0 || tickIntervalMs <= 0 || chargeBonusRage < 0) return null;
|
|
|
|
let chargeDamageReductionPercent = 0;
|
|
let chargeDamageReductionDurationMs = 0;
|
|
let chargeDamageReductionSpellId: number | null = null;
|
|
if (safeRank === rankCount) {
|
|
const reduction = description.match(/Charge also reduces your damage taken by (\d+)% for (\d+) seconds/i);
|
|
if (reduction) {
|
|
chargeDamageReductionPercent = Number(reduction[1]);
|
|
chargeDamageReductionDurationMs = Number(reduction[2]) * 1_000;
|
|
chargeDamageReductionSpellId = rankEffect.effects.find((effect) => effect.triggerSpellId > 0)?.triggerSpellId ?? null;
|
|
}
|
|
}
|
|
|
|
return {
|
|
rank: safeRank,
|
|
ragePerTick,
|
|
tickIntervalMs,
|
|
chargeBonusRage,
|
|
chargeDamageReductionPercent,
|
|
chargeDamageReductionDurationMs,
|
|
chargeDamageReductionSpellId,
|
|
};
|
|
}
|