Update Healer Man server
This commit is contained in:
1 parent
39458d5de6
commit
6cc1b9217f
203 files changed
+133929
-31271
No files matched your search
@@ -0,0 +1,251 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { deriveRomNativeStats } from "./romNativeStats";
|
||||
import { EMPTY_ITEM_STATS } from "./itemStats";
|
||||
import {
|
||||
nativePeriodicDamageSnapshot,
|
||||
nativePeriodicDamageTick,
|
||||
romNativePeriodicDamageBinding,
|
||||
romNativePeriodicDamageComponent,
|
||||
type RomNativePeriodicDamageSource,
|
||||
} from "./romNativePeriodicDamage";
|
||||
|
||||
const LEVEL = 30;
|
||||
|
||||
function nativeStats(classId: "rom-mage" | "rom-rogue" = "rom-mage") {
|
||||
const stats = deriveRomNativeStats({
|
||||
classId,
|
||||
level: LEVEL,
|
||||
raceId: "rom-human",
|
||||
gender: "male",
|
||||
gear: EMPTY_ITEM_STATS.romNative,
|
||||
});
|
||||
return {
|
||||
...stats,
|
||||
attributes: { ...stats.attributes, wisdom: 100 },
|
||||
health: { max: 1_000 },
|
||||
physicalAttack: 400,
|
||||
physicalDamage: 200,
|
||||
physicalDefense: 200,
|
||||
magicalAttack: 500,
|
||||
magicalDamage: 100,
|
||||
magicalDefense: 100,
|
||||
healing: {
|
||||
...stats.healing,
|
||||
healPowerRate: 1,
|
||||
healPoint: 0,
|
||||
targetHealAbsorbRate: 0,
|
||||
targetHealAbsorb: 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function sourceFor(componentId: number, sourceSkillId: number): RomNativePeriodicDamageSource {
|
||||
const binding = romNativePeriodicDamageBinding({ skillId: sourceSkillId, componentId, kind: "dot" });
|
||||
if (!binding) throw new Error(`Missing generated DotMagic binding ${sourceSkillId}:${componentId}.`);
|
||||
return binding;
|
||||
}
|
||||
|
||||
describe("native RoM periodic damage", () => {
|
||||
it("exports exact active-five source rows and can find shared component metadata", () => {
|
||||
const binding = sourceFor(620313, 490306);
|
||||
expect(binding).toMatchObject({
|
||||
abilityId: "rom-rogue-general-0",
|
||||
classId: "rom-rogue",
|
||||
rank: 1,
|
||||
sourceSkillId: 490306,
|
||||
component: { componentId: 620313, kind: "dot", dotType: 0, dotBase: -5, dotTimeSeconds: 2 },
|
||||
});
|
||||
expect(romNativePeriodicDamageComponent(620313)).toEqual(binding.component);
|
||||
expect(romNativePeriodicDamageBinding({ skillId: 490306, componentId: 620313, kind: "hot" })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("snapshots magical target resistance and magic-versus-defense power into Buff.Power", () => {
|
||||
const source = sourceFor(501550, 491168);
|
||||
const result = nativePeriodicDamageSnapshot({
|
||||
source,
|
||||
caster: nativeStats(),
|
||||
target: nativeStats("rom-rogue"),
|
||||
sourceIsPlayer: true,
|
||||
magicLevel: 10,
|
||||
casterLevel: 30,
|
||||
targetLevel: 30,
|
||||
spellTimeSeconds: 2,
|
||||
effectiveMagicPower: 1,
|
||||
magicResistance: { value: 5, rateFactor: 0.9 },
|
||||
});
|
||||
expect(result.status).toBe("supported");
|
||||
if (result.status !== "supported") return;
|
||||
expect(result.value).toMatchObject({
|
||||
componentId: 501550,
|
||||
sourceSkillId: 491168,
|
||||
magicLevel: 10,
|
||||
spellTime: 2,
|
||||
effectTimeSeconds: 12,
|
||||
tickIntervalSeconds: 2,
|
||||
ticks: 6,
|
||||
rankCorrectionTicks: 6,
|
||||
magicLevelAdjustment: 16,
|
||||
buffPower: 26,
|
||||
sourceTickDamage: 91,
|
||||
targetDefenseStage: "dot-application-rank-correction",
|
||||
tickEventType: "magic",
|
||||
});
|
||||
expect(nativePeriodicDamageTick(result.value)).toMatchObject({
|
||||
damage: 91,
|
||||
healthDelta: -91,
|
||||
firesPeriodicAttackEvents: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps raw duration rank-correction ticks distinct from the applied effect duration", () => {
|
||||
const source = sourceFor(620313, 490306);
|
||||
const shifted = {
|
||||
...source,
|
||||
component: {
|
||||
...source.component,
|
||||
effectTimeSeconds: 20,
|
||||
effectTimeSkillLevelArg: 50,
|
||||
levelDeltaEffectTime: 1,
|
||||
isFixDotPower: true,
|
||||
},
|
||||
};
|
||||
const result = nativePeriodicDamageSnapshot({
|
||||
source: shifted,
|
||||
caster: nativeStats("rom-rogue"),
|
||||
target: nativeStats(),
|
||||
sourceIsPlayer: true,
|
||||
magicLevel: 10,
|
||||
casterLevel: 30,
|
||||
targetLevel: 20,
|
||||
spellTimeSeconds: 1,
|
||||
});
|
||||
expect(result.status).toBe("supported");
|
||||
if (result.status !== "supported") return;
|
||||
expect(result.value.effectTimeSeconds).toBe(110);
|
||||
expect(result.value.ticks).toBe(55);
|
||||
expect(result.value.rankCorrectionTicks).toBe(10);
|
||||
});
|
||||
|
||||
it("uses Main_DMG and Main_ATKSpeed for physical DotMagic rank correction", () => {
|
||||
const source = sourceFor(620313, 490306);
|
||||
const physical = {
|
||||
...source,
|
||||
component: { ...source.component, effectType: 1, magicType: -1, dotBase: -10, dotSkillLevelArg: 50 },
|
||||
};
|
||||
const result = nativePeriodicDamageSnapshot({
|
||||
source: physical,
|
||||
caster: nativeStats("rom-rogue"),
|
||||
target: nativeStats(),
|
||||
sourceIsPlayer: true,
|
||||
magicLevel: 10,
|
||||
casterLevel: 30,
|
||||
targetLevel: 30,
|
||||
spellTimeSeconds: 2,
|
||||
mainDamage: 200,
|
||||
mainAttackSpeed: 20,
|
||||
});
|
||||
expect(result.status).toBe("supported");
|
||||
if (result.status !== "supported") return;
|
||||
expect(result.value).toMatchObject({
|
||||
rankCorrectionTicks: 5,
|
||||
magicLevelAdjustment: 38,
|
||||
buffPower: 48,
|
||||
sourceTickDamage: 250,
|
||||
tickEventType: "skill",
|
||||
});
|
||||
});
|
||||
|
||||
it("uses raw MagicLv for fixed-power rows and ShowPowerLv suppresses it", () => {
|
||||
const source = sourceFor(501550, 491168);
|
||||
const fixed = { ...source, component: { ...source.component, isFixDotPower: true } };
|
||||
const fixedResult = nativePeriodicDamageSnapshot({
|
||||
source: fixed, caster: nativeStats(), target: nativeStats("rom-rogue"), sourceIsPlayer: true,
|
||||
magicLevel: 10, casterLevel: 30, targetLevel: 30,
|
||||
});
|
||||
expect(fixedResult.status).toBe("supported");
|
||||
if (fixedResult.status === "supported") {
|
||||
expect(fixedResult.value).toMatchObject({ magicLevelAdjustment: 0, buffPower: 10, sourceTickDamage: 40 });
|
||||
}
|
||||
|
||||
const shown = { ...source, component: { ...source.component, isShowPowerLevel: true } };
|
||||
const shownResult = nativePeriodicDamageSnapshot({
|
||||
source: shown, caster: nativeStats(), target: nativeStats("rom-rogue"), sourceIsPlayer: true,
|
||||
magicLevel: 10, casterLevel: 30, targetLevel: 30,
|
||||
spellTimeSeconds: 2, effectiveMagicPower: 1, magicResistance: { value: 0, rateFactor: 1 },
|
||||
});
|
||||
expect(shownResult.status).toBe("supported");
|
||||
if (shownResult.status === "supported") {
|
||||
expect(shownResult.value).toMatchObject({ buffPower: 0, sourceTickDamage: 8 });
|
||||
}
|
||||
});
|
||||
|
||||
it("uses current MaxHP for HP_Per pulses and never rerolls source critical damage", () => {
|
||||
const source = sourceFor(500549, 490304);
|
||||
const result = nativePeriodicDamageSnapshot({
|
||||
source, caster: nativeStats("rom-rogue"), target: nativeStats(), sourceIsPlayer: true,
|
||||
magicLevel: 10, casterLevel: 30, targetLevel: 30,
|
||||
});
|
||||
expect(result.status).toBe("supported");
|
||||
if (result.status !== "supported") return;
|
||||
expect(result.value).toMatchObject({ percentHealthDot: true, sourceTickDamage: 6, tickDamageAtApplication: 60 });
|
||||
expect(nativePeriodicDamageTick(result.value, 1_500)).toMatchObject({
|
||||
damage: 90,
|
||||
healthDelta: -90,
|
||||
firesPeriodicAttackEvents: true,
|
||||
});
|
||||
expect(nativePeriodicDamageTick(result.value, Number.NaN)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("fails closed when source-required elemental resistance or physical weapon inputs are missing", () => {
|
||||
const elemental = nativePeriodicDamageSnapshot({
|
||||
source: sourceFor(501550, 491168), caster: nativeStats(), target: nativeStats("rom-rogue"),
|
||||
sourceIsPlayer: true, magicLevel: 10, casterLevel: 30, targetLevel: 30,
|
||||
spellTimeSeconds: 2, effectiveMagicPower: 1,
|
||||
});
|
||||
expect(elemental).toEqual({ status: "unsupported", reason: "periodic-damage-magical-resistance-required" });
|
||||
|
||||
const physicalSource = sourceFor(620313, 490306);
|
||||
const physical = nativePeriodicDamageSnapshot({
|
||||
source: { ...physicalSource, component: { ...physicalSource.component, effectType: 1 } },
|
||||
caster: nativeStats("rom-rogue"), target: nativeStats(), sourceIsPlayer: true,
|
||||
magicLevel: 10, casterLevel: 30, targetLevel: 30,
|
||||
});
|
||||
expect(physical).toEqual({ status: "unsupported", reason: "periodic-damage-physical-weapon-profile-required" });
|
||||
});
|
||||
|
||||
it("fails closed when a dot refresh can retain or increase the source buff power", () => {
|
||||
const result = nativePeriodicDamageSnapshot({
|
||||
source: sourceFor(620313, 490306), caster: nativeStats("rom-rogue"), target: nativeStats(),
|
||||
sourceIsPlayer: true, magicLevel: 10, casterLevel: 30, targetLevel: 30, isReapplication: true,
|
||||
});
|
||||
expect(result).toEqual({ status: "unsupported", reason: "periodic-damage-reapplication-stacking-unsupported" });
|
||||
});
|
||||
|
||||
it("keeps the player cap at 80 while accepting NPC levels within exported source bounds", () => {
|
||||
const source = sourceFor(620313, 490306);
|
||||
const enemyBoundedSource = {
|
||||
...source,
|
||||
maxCasterLevel: 100,
|
||||
maxMagicLevel: 100,
|
||||
component: { ...source.component, isFixDotPower: true },
|
||||
};
|
||||
const npcResult = nativePeriodicDamageSnapshot({
|
||||
source: enemyBoundedSource, caster: nativeStats(), target: nativeStats(), sourceIsPlayer: false,
|
||||
magicLevel: 90, casterLevel: 90, targetLevel: 90,
|
||||
});
|
||||
expect(npcResult.status).toBe("supported");
|
||||
|
||||
const playerResult = nativePeriodicDamageSnapshot({
|
||||
source: enemyBoundedSource, caster: nativeStats(), target: nativeStats(), sourceIsPlayer: true,
|
||||
magicLevel: 90, casterLevel: 90, targetLevel: 90,
|
||||
});
|
||||
expect(playerResult).toEqual({ status: "unsupported", reason: "periodic-damage-input-invalid" });
|
||||
|
||||
const unboundedNpcResult = nativePeriodicDamageSnapshot({
|
||||
source: { ...source, component: { ...source.component, isFixDotPower: true } },
|
||||
caster: nativeStats(), target: nativeStats(), sourceIsPlayer: false,
|
||||
magicLevel: 90, casterLevel: 90, targetLevel: 90,
|
||||
});
|
||||
expect(unboundedNpcResult).toEqual({ status: "unsupported", reason: "periodic-damage-npc-source-bounds-required" });
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user