Update class mechanics, stat rulesets, and CoA reference catalogs
This commit is contained in:
1 parent
b16821e92e
commit
39458d5de6
221 files changed
+2584962
-568
No files matched your search
@@ -4,10 +4,16 @@ import {
|
||||
baseClassFor,
|
||||
classById,
|
||||
isRomClassId,
|
||||
isRomRaceId,
|
||||
type ClassId,
|
||||
type GenderId,
|
||||
type RaceId,
|
||||
} from "../app/characterCatalog";
|
||||
import { normalizeCharacterStatRuleset, type CharacterStatRuleset } from "./characterStatRuleset";
|
||||
import { romDualClassBaseStats } from "./romStats";
|
||||
import { applyRomNativeStatModifiers, deriveRomNativeStats, type RomNativeDerivedStats, type RomNativeStatModifiers } from "./romNativeStats";
|
||||
import { romNativeGearFromSharedDirectStats } from "./romNativeStatsTypes";
|
||||
import { ROM_SECONDARY_UNLOCK_LEVEL } from "../app/romDualClass";
|
||||
import baseStatsData from "./generated/wow335BaseStats.json";
|
||||
import combatRatingsData from "./generated/wow335CombatRatings.json";
|
||||
import {
|
||||
@@ -36,6 +42,10 @@ export interface CharacterStatPercentModifiers {
|
||||
}
|
||||
|
||||
export interface CharacterStats extends PrimaryAttributes {
|
||||
/** Source-unit RoM channels; values are never interpreted as WoW ratings. */
|
||||
readonly romNative?: RomNativeDerivedStats;
|
||||
/** Shared combat only: ATK/MDMG pass-through and DEF uses the WotLK armor curve. */
|
||||
readonly combatStatModel?: "rom-native-normalized";
|
||||
readonly level: number;
|
||||
readonly health: number;
|
||||
readonly mana: number;
|
||||
@@ -286,6 +296,87 @@ function spiritManaPer5(classId: ClassId, level: number, intellect: number, spir
|
||||
return 5 * Math.sqrt(Math.max(1, intellect)) * Math.max(0, spirit) * coefficient;
|
||||
}
|
||||
|
||||
function nativeCharacterStats(
|
||||
classId: Extract<ClassId, `rom-${string}`>,
|
||||
level: number,
|
||||
gear: ItemStats,
|
||||
raceId: RaceId,
|
||||
modifiers: CharacterStatPercentModifiers,
|
||||
secondaryClassId: ClassId | null | undefined,
|
||||
gender: GenderId,
|
||||
): CharacterStats {
|
||||
const nativeBase = deriveRomNativeStats({
|
||||
classId,
|
||||
level,
|
||||
raceId: isRomRaceId(raceId) ? raceId : "rom-human",
|
||||
gender,
|
||||
secondaryClassId: safeLevel(level) >= ROM_SECONDARY_UNLOCK_LEVEL
|
||||
&& secondaryClassId && isRomClassId(secondaryClassId) ? secondaryClassId : null,
|
||||
// Native payload wins wholesale; direct legacy ItemStats calls receive
|
||||
// only the verified flat-primary/HP/MP shared-stat fallback.
|
||||
gear: gear.romNative ?? romNativeGearFromSharedDirectStats(gear),
|
||||
});
|
||||
const percentFactor = (value: number | undefined) => Math.max(0, 1 + (Number.isFinite(value) ? value! : 0) / 100);
|
||||
const rates: NonNullable<RomNativeStatModifiers["rates"]> = {
|
||||
strength: percentFactor(modifiers.strength),
|
||||
stamina: percentFactor(modifiers.stamina),
|
||||
agility: percentFactor(modifiers.agility),
|
||||
intellect: percentFactor(modifiers.intellect),
|
||||
health: percentFactor(modifiers.maxHealth),
|
||||
mana: percentFactor(modifiers.maxMana),
|
||||
};
|
||||
const native = applyRomNativeStatModifiers(nativeBase, { rates });
|
||||
const safe = safeLevel(level);
|
||||
const physicalDefense = native.physicalDefense;
|
||||
return Object.freeze({
|
||||
level: safe,
|
||||
strength: native.attributes.strength,
|
||||
agility: native.attributes.agility,
|
||||
stamina: native.attributes.stamina,
|
||||
intellect: native.attributes.intellect,
|
||||
// RoM Wisdom remains in the native sidecar; it is not WoW Spirit.
|
||||
spirit: 0,
|
||||
health: native.health.max,
|
||||
mana: native.usesMana ? native.mana.max : 0,
|
||||
healthPer5: 0,
|
||||
manaPer5: 0,
|
||||
attackPower: native.physicalAttack,
|
||||
rangedAttackPower: native.physicalAttack,
|
||||
spellPower: native.magicalDamage,
|
||||
meleeCritPercent: 0,
|
||||
rangedCritPercent: 0,
|
||||
spellCritPercent: 0,
|
||||
meleeHitPercent: 0,
|
||||
rangedHitPercent: 0,
|
||||
spellHitPercent: 0,
|
||||
meleeHastePercent: 0,
|
||||
rangedHastePercent: 0,
|
||||
spellHastePercent: 0,
|
||||
expertise: 0,
|
||||
expertiseReductionPercent: 0,
|
||||
armorPenetrationPercent: 0,
|
||||
spellPenetration: 0,
|
||||
armor: physicalDefense,
|
||||
// Shared dungeon combat deliberately applies the existing WotLK armor curve to native DEF.
|
||||
armorReductionPercent: armorReductionPercent(physicalDefense, safe),
|
||||
defenseSkill: 0,
|
||||
dodgePercent: 0,
|
||||
parryPercent: 0,
|
||||
blockPercent: 0,
|
||||
blockValue: 0,
|
||||
resiliencePercent: 0,
|
||||
arcaneResistance: 0,
|
||||
fireResistance: 0,
|
||||
frostResistance: 0,
|
||||
natureResistance: 0,
|
||||
shadowResistance: 0,
|
||||
meleeWeapon: gear.meleeWeapon,
|
||||
rangedWeapon: gear.rangedWeapon,
|
||||
romNative: native,
|
||||
combatStatModel: "rom-native-normalized",
|
||||
});
|
||||
}
|
||||
|
||||
export function deriveCharacterStats(
|
||||
classId: ClassId,
|
||||
level: number,
|
||||
@@ -293,8 +384,13 @@ export function deriveCharacterStats(
|
||||
raceId: RaceId = "human",
|
||||
modifiers: CharacterStatPercentModifiers = {},
|
||||
secondaryClassId?: ClassId | null,
|
||||
gender: GenderId = "male",
|
||||
statRuleset: CharacterStatRuleset = "wow",
|
||||
): CharacterStats {
|
||||
const safe = safeLevel(level);
|
||||
if (normalizeCharacterStatRuleset(statRuleset, classId) === "rom" && isRomClassId(classId)) {
|
||||
return nativeCharacterStats(classId, safe, gear, raceId, modifiers, secondaryClassId, gender);
|
||||
}
|
||||
const hybridBase = romHybridBase(classId, secondaryClassId, safe);
|
||||
const romBase = hybridBase ? null : romDualClassBaseStats(classId, secondaryClassId, safe);
|
||||
const base = hybridBase?.attributes ?? (romBase ? {
|
||||
|
||||
Reference in new issue
Block a user