forked from phenom/healer-man
Update Healer Man server
This commit is contained in:
1 parent
e284f4fc76
commit
15a5254380
52 files changed
+637
-80
No files matched your search
@@ -139,6 +139,10 @@ export interface CombatPresentationEvent {
|
||||
readonly targetPosition?: CombatPresentationPosition;
|
||||
readonly radius?: number;
|
||||
readonly durationMs?: number;
|
||||
/** Scripted visual-only projectile travel; ordinary spell travel keeps its distance-based timing. */
|
||||
readonly visualTravelDurationMs?: number;
|
||||
/** Scripted visual-only source scale; ordinary spells keep the packaged scale. */
|
||||
readonly visualScale?: number;
|
||||
/** The cast resolved immediately, so its source cast visual may overlap release. */
|
||||
readonly instantCast?: boolean;
|
||||
}
|
||||
|
||||
@@ -186,6 +186,21 @@ describe("Bite-Sized race mechanics", () => {
|
||||
expect(kind === "ring" ? distance < 4 || distance > 7 : distance > effect.radius).toBe(true);
|
||||
});
|
||||
|
||||
it.each(["falling-stars", "stormcall"])("retains the %s strike visual without repeating damage", (movementId) => {
|
||||
start("last-stand", movementId);
|
||||
advanceDungeonRaceAffixes(20_000, []);
|
||||
advanceDungeonRaceAffixes(22_000, []);
|
||||
const healthAfterStrike = useCombatStore.getState().health;
|
||||
const effect = useDungeonRaceAffixStore.getState().effects[0];
|
||||
expect(effect.tickAt).toBe(22_000);
|
||||
expect(effect.expiresAt).toBe(24_800);
|
||||
advanceDungeonRaceAffixes(23_000, []);
|
||||
expect(useCombatStore.getState().health).toBe(healthAfterStrike);
|
||||
expect(useDungeonRaceAffixStore.getState().effects).toHaveLength(1);
|
||||
advanceDungeonRaceAffixes(24_800, []);
|
||||
expect(useDungeonRaceAffixStore.getState().effects).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("freezes a warning and the next hazard across an offline pause", () => {
|
||||
start("volatile-remains");
|
||||
deadTrash(trashIds[0]);
|
||||
|
||||
@@ -137,8 +137,11 @@ function createMovementEffect(id: string, kind: string, now: number, targetRoll:
|
||||
const target = actors[targetRoll % actors.length];
|
||||
const base = { id, position: target.position, createdAt: now, resolvesAt: now + 2_000,
|
||||
expiresAt: now + 2_500, radius: 3 };
|
||||
if (kind === "falling-stars") return { ...base, kind: "star" };
|
||||
if (kind === "stormcall") return { ...base, kind: "storm", targetId: target.id };
|
||||
// Retain resolved strikes long enough for their source impact animation to finish.
|
||||
// tickAt still makes damage a one-time event.
|
||||
if (kind === "falling-stars") return { ...base, kind: "star", expiresAt: now + 4_800 };
|
||||
if (kind === "stormcall") return { ...base, kind: "storm", targetId: target.id,
|
||||
expiresAt: now + 4_800 };
|
||||
if (kind === "arcane-pulse") return { ...base, kind: "ring", radius: 7 };
|
||||
return { ...base, kind: "predator", targetId: target.id, radius: 2,
|
||||
expiresAt: now + 8_000, tickAt: now + 3_000,
|
||||
|
||||
@@ -49,7 +49,8 @@ describe("RoM bite-sized finales", () => {
|
||||
expect(dungeonContentPackId(id)).toBe(`dungeon:${source.id}`);
|
||||
expect(slice.assets).toBe(source.assets);
|
||||
expect(slice.staticSpawns).toHaveLength(id === "bite-sized-barren-caves-anglo" ? 4
|
||||
: id === "bite-sized-kawaks-tomb-linobia" || id === "bite-sized-hall-of-survivors-pursuer" ? 5
|
||||
: id === "bite-sized-kawaks-tomb-linobia" ? 5
|
||||
: id === "bite-sized-hall-of-survivors-pursuer" ? 7
|
||||
: id === "bite-sized-hoto-teeth" ? 10 : 6);
|
||||
expect(slice.staticSpawns.every((spawn) => source.staticSpawns.some((candidate) =>
|
||||
candidate.id === spawn.id && candidate.entityId === spawn.entityId))).toBe(true);
|
||||
@@ -120,17 +121,28 @@ describe("RoM bite-sized finales", () => {
|
||||
}
|
||||
}, 30_000);
|
||||
|
||||
it("stages both Hall packs and the finale in the twin-statue chamber", () => {
|
||||
it("stages three Hall packs from the balcony to the twin-statue finale", () => {
|
||||
const slice = DUNGEON_REGISTRY["bite-sized-hall-of-survivors-pursuer"];
|
||||
const balcony = slice.staticSpawns.filter((spawn) => spawn.id.includes("119628"));
|
||||
const landing = slice.staticSpawns.filter((spawn) => spawn.id.includes("599423"));
|
||||
const guardians = slice.staticSpawns.filter((spawn) => spawn.id.includes("59919"));
|
||||
const hunters = slice.staticSpawns.filter((spawn) => spawn.id.includes("119628"));
|
||||
expect(balcony).toHaveLength(2);
|
||||
expect(landing).toHaveLength(2);
|
||||
expect(guardians).toHaveLength(3);
|
||||
expect(hunters).toHaveLength(2);
|
||||
// The western side room lies beyond x=-185; the statue chamber is to its east.
|
||||
expect([...slice.staticSpawns, slice.encounterSpawnGroups![0].spawns[0]].every(
|
||||
(spawn) => spawn.position[0] > -185 && spawn.position[0] < -158)).toBe(true);
|
||||
expect(Math.min(...hunters.map((spawn) => spawn.position[2]))
|
||||
- Math.max(...guardians.map((spawn) => spawn.position[2]))).toBeGreaterThan(10);
|
||||
expect(slice.entrance.footPosition[1]).toBeGreaterThan(0);
|
||||
expect(balcony.every((spawn) => spawn.position[1] > 0)).toBe(true);
|
||||
expect(landing.every((spawn) => spawn.position[1] > -7 && spawn.position[1] < -3)).toBe(true);
|
||||
expect(guardians.every((spawn) => spawn.position[1] < -17)).toBe(true);
|
||||
expect(Math.max(...balcony.map((spawn) => spawn.position[0])))
|
||||
.toBeLessThan(Math.min(...landing.map((spawn) => spawn.position[0])));
|
||||
expect(Math.max(...landing.map((spawn) => spawn.position[0])))
|
||||
.toBeLessThan(Math.min(...guardians.map((spawn) => spawn.position[0])));
|
||||
const boss = slice.encounterSpawnGroups![0].spawns[0];
|
||||
expect(boss.position[0]).toBeGreaterThan(-174);
|
||||
expect(boss.position[0]).toBeLessThan(-166);
|
||||
expect(boss.position[2]).toBeGreaterThan(15);
|
||||
expect(boss.position[2]).toBeLessThan(25);
|
||||
expect(boss.position[1]).toBeLessThan(-17);
|
||||
});
|
||||
|
||||
it("fills Hoto with three readable source Ayam clusters and a cooler cavern palette", () => {
|
||||
|
||||
@@ -190,23 +190,31 @@ const ROM_SLICES: readonly RoMSlice[] = [
|
||||
title: "Hall of Survivors: Pursuer Ward",
|
||||
location: "Hall of Survivors · Pursuer Ward",
|
||||
entranceName: "Pursuer Ward",
|
||||
start: [-155, -17.55, 35],
|
||||
forward: [-1, 0, 0],
|
||||
// Begin on the upper overlook before descending the connected east stair.
|
||||
start: [-220, 0.554, 40],
|
||||
forward: [1, 0, 0],
|
||||
finaleBossName: "Divine Punishment Pursuer",
|
||||
finalePosition: [-171, -17.55, 31],
|
||||
// Open floor on the axis between the two giant statues, facing the ornate doorway.
|
||||
finalePosition: [-170, -17.55, 20],
|
||||
mobSpawnIds: [
|
||||
"rw-hall-of-survivors-spawn-11962803", "rw-hall-of-survivors-spawn-11962817",
|
||||
"rw-hall-of-survivors-spawn-5994236", "rw-hall-of-survivors-spawn-5994237",
|
||||
"rw-hall-of-survivors-spawn-5991940", "rw-hall-of-survivors-spawn-5991941",
|
||||
"rw-hall-of-survivors-spawn-5991939",
|
||||
"rw-hall-of-survivors-spawn-11962803", "rw-hall-of-survivors-spawn-11962817",
|
||||
],
|
||||
// The two Hunters originally stood in the western side room. Reposition them
|
||||
// as a distinct pack at the far end of the open chamber with the statues.
|
||||
// Two Hunters guard the balcony, two Rune Swordsmen hold the stair landing,
|
||||
// and three Rune Guardians guard the lower room before the statues.
|
||||
mobSpawnPositions: {
|
||||
"rw-hall-of-survivors-spawn-11962803": [-165, -17.55, 38],
|
||||
"rw-hall-of-survivors-spawn-11962817": [-175, -17.55, 38],
|
||||
"rw-hall-of-survivors-spawn-11962803": [-218, 0.554, 52],
|
||||
"rw-hall-of-survivors-spawn-11962817": [-222, 0.554, 55],
|
||||
"rw-hall-of-survivors-spawn-5994236": [-197, -4.2, 52],
|
||||
"rw-hall-of-survivors-spawn-5994237": [-193, -4.2, 55],
|
||||
"rw-hall-of-survivors-spawn-5991940": [-158, -17.55, 45],
|
||||
"rw-hall-of-survivors-spawn-5991941": [-154, -17.55, 44],
|
||||
"rw-hall-of-survivors-spawn-5991939": [-156, -17.55, 40],
|
||||
},
|
||||
summary: "Clear two packs around the ward's twin statues, then defeat one surprise Hall boss in the chamber.",
|
||||
adaptation: "The Hunters and selected boss are moved out of the western side room into the statue chamber. Other Pursuer and Seeker bosses, earlier rooms, and combined source scripts are outside this short run.",
|
||||
summary: "Clear the overlook, stair landing, and lower ward packs, then defeat one surprise Hall boss between the twin statues.",
|
||||
adaptation: "The Hunters, Rune Swordsmen, Rune Guardians, and selected boss stage a descending three-pack route from the balcony into the twin-statue chamber. Other Pursuer and Seeker bosses, earlier rooms, and combined source scripts are outside this short run.",
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
Reference in new issue
Block a user