446 lines
20 KiB
TypeScript
446 lines
20 KiB
TypeScript
import { beforeEach, describe, expect, it } from "vitest";
|
|
import { applyAura } from "./combatAuras";
|
|
import { abilitiesForClass } from "./abilityCatalog";
|
|
import { useCombatStore } from "./combatStore";
|
|
import { damageAfterArmor } from "./itemStats";
|
|
import {
|
|
clearPartyRuntimeRegistry,
|
|
registerPartyRuntimePosition,
|
|
} from "./partyRuntimeRegistry";
|
|
import { clearMobRuntimeRegistry, setMobPosition } from "./mobRuntimeRegistry";
|
|
import { usePartyStore } from "./partyStore";
|
|
import { wow335HunterPetProwlAura, wow335HunterPetRakeDamage } from "./wow335HunterPetRuntime";
|
|
|
|
function callPet(now = 1_000) {
|
|
const ability = useCombatStore.getState().abilities.find((entry) => entry.name === "Call Pet");
|
|
if (!ability) throw new Error("Hunter Call Pet is missing from the active build.");
|
|
const result = useCombatStore.getState().castAbility(ability.id, [0, 0, 0], now);
|
|
if (!result.ok) throw new Error(`Call Pet failed: ${result.reason ?? "unknown"}`);
|
|
const pet = useCombatStore.getState().summons.find((summon) => summon.hunterPet);
|
|
if (!pet) throw new Error("Call Pet did not create a Hunter pet.");
|
|
return pet;
|
|
}
|
|
|
|
describe("selectable Hunter pet families", () => {
|
|
beforeEach(() => {
|
|
clearMobRuntimeRegistry();
|
|
clearPartyRuntimeRegistry();
|
|
useCombatStore.getState().initializeCharacter({ classId: "hunter", level: 80, xp: 0 });
|
|
});
|
|
|
|
it("keeps Wolf as the legacy default, then calls the explicitly selected Turtle profile", () => {
|
|
expect(useCombatStore.getState().hunterPetFamily).toBe("wolf");
|
|
expect(useCombatStore.getState().setHunterPetFamily("cat")).toBe(true);
|
|
expect(useCombatStore.getState().setHunterPetFamily("turtle")).toBe(true);
|
|
const pet = callPet();
|
|
|
|
expect(pet).toMatchObject({
|
|
name: "Turtle",
|
|
hunterPet: {
|
|
family: "turtle",
|
|
shellShield: { spellId: 26_064, focusCost: 0, cooldownMs: 60_000, durationMs: 12_000 },
|
|
},
|
|
});
|
|
expect(pet.hunterPet?.furiousHowl).toBeUndefined();
|
|
expect(useCombatStore.getState().setHunterPetFamily("wolf")).toBe(true);
|
|
expect(useCombatStore.getState().summons.find((summon) => summon.id === pet.id)).toMatchObject({
|
|
name: "Wolf",
|
|
hunterPet: { family: "wolf", furiousHowl: { spellId: 64_495 } },
|
|
});
|
|
});
|
|
|
|
it("autocasts source-backed Shell Shield in combat and prevents a family swap until combat ends", () => {
|
|
expect(useCombatStore.getState().setHunterPetFamily("turtle")).toBe(true);
|
|
const pet = callPet();
|
|
useCombatStore.getState().registerMob("turtle-shell-target", {
|
|
name: "Shell Shield target",
|
|
maxHealth: 100_000,
|
|
xpReward: 0,
|
|
});
|
|
setMobPosition("turtle-shell-target", [2, 0, 0]);
|
|
useCombatStore.getState().selectMob("turtle-shell-target");
|
|
expect(useCombatStore.getState().commandHunterPet(pet.id, "attack")).toBe(true);
|
|
useCombatStore.getState().tick(0.1, 1_500);
|
|
|
|
const activePet = useCombatStore.getState().summons.find((summon) => summon.id === pet.id)!;
|
|
expect(activePet.hunterPetShellShieldReadyAt).toBe(61_500);
|
|
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
|
targetId: pet.id,
|
|
definition: expect.objectContaining({
|
|
id: "wow335-hunter-pet-shell-shield",
|
|
durationMs: 12_000,
|
|
modifiers: [expect.objectContaining({ kind: "damage", direction: "taken", value: -0.5 })],
|
|
}),
|
|
}));
|
|
expect(useCombatStore.getState().mobs["turtle-shell-target"]?.engaged).toBe(true);
|
|
expect(useCombatStore.getState().setHunterPetFamily("wolf")).toBe(false);
|
|
expect(useCombatStore.getState().hunterPetFamily).toBe("turtle");
|
|
|
|
expect(useCombatStore.getState().damageSummon(pet.id, 1_000)).toBe(500);
|
|
});
|
|
|
|
it("revives the player's persisted Cat while generated companion pets remain Wolf", () => {
|
|
useCombatStore.getState().initializeCharacter({
|
|
classId: "hunter",
|
|
specializationId: "hunter:marksmanship",
|
|
level: 80,
|
|
xp: 0,
|
|
hunterPetFamily: "cat",
|
|
});
|
|
const calledPet = callPet();
|
|
const petId = "__player__:summon-1";
|
|
useCombatStore.setState((state) => ({
|
|
summons: state.summons.map((summon) => summon.id === calledPet.id ? { ...summon, id: petId } : summon),
|
|
}));
|
|
const pet = { ...calledPet, id: petId };
|
|
useCombatStore.setState((state) => ({
|
|
summons: state.summons.map((summon) => summon.id === pet.id ? { ...summon, health: 0 } : summon),
|
|
}));
|
|
const revive = useCombatStore.getState().abilities.find((ability) => ability.name === "Revive Pet");
|
|
if (!revive) throw new Error("Hunter Revive Pet is missing from the active build.");
|
|
const startAt = 10_000;
|
|
const result = useCombatStore.getState().castAbility(revive.id, [0, 0, 0], startAt);
|
|
expect(result.ok, result.reason).toBe(true);
|
|
const cast = useCombatStore.getState().activeCast;
|
|
if (cast) useCombatStore.getState().tick((cast.completesAt - startAt) / 1_000 + 0.1, cast.completesAt + 1);
|
|
expect(useCombatStore.getState().summons.find((summon) => summon.id === pet.id)).toMatchObject({
|
|
name: "Cat",
|
|
hunterPet: { family: "cat", spellId: 52_472, rake: { spellId: 59_886 }, prowl: { spellId: 24_453 } },
|
|
});
|
|
|
|
usePartyStore.getState().initializeParty("turtle-family-player-only", 80, useCombatStore.getState().health);
|
|
const companionId = usePartyStore.getState().members[0]!.id;
|
|
usePartyStore.setState((state) => ({
|
|
members: state.members.map((member) => member.id === companionId
|
|
? { ...member, classId: "hunter", secondaryClassId: null, level: 80, raceId: "human", talentRanks: {} }
|
|
: member),
|
|
}));
|
|
registerPartyRuntimePosition(companionId, [0, 0, 0]);
|
|
useCombatStore.getState().synchronizeActors();
|
|
const companionCall = abilitiesForClass("hunter").find((ability) => ability.name === "Call Pet")!;
|
|
const companionPetId = useCombatStore.getState().summonActor(companionId, companionCall, {
|
|
kind: "summon",
|
|
creatureId: 1,
|
|
coefficient: 1,
|
|
durationMs: 60_000,
|
|
}, [0, 0, 0], null, 20_000);
|
|
expect(useCombatStore.getState().summons.find((summon) => summon.id === companionPetId)).toMatchObject({
|
|
name: "Wolf",
|
|
hunterPet: { family: "wolf", furiousHowl: { spellId: 64_495 } },
|
|
});
|
|
});
|
|
|
|
it("clears player Cat Prowl on family swaps and dismissal without stripping companion auras", () => {
|
|
expect(useCombatStore.getState().setHunterPetFamily("cat")).toBe(true);
|
|
const calledPet = callPet();
|
|
const playerPetId = "__player__:summon-1";
|
|
const companionPetId = "companion-cat-pet";
|
|
const prowl = calledPet.hunterPet?.prowl;
|
|
if (!prowl) throw new Error("Cat Prowl profile is missing.");
|
|
const playerProwl = applyAura([], {
|
|
definition: wow335HunterPetProwlAura(prowl),
|
|
sourceId: playerPetId,
|
|
targetId: playerPetId,
|
|
now: 1_000,
|
|
}).aura;
|
|
const companionProwl = applyAura([], {
|
|
definition: wow335HunterPetProwlAura(prowl),
|
|
sourceId: companionPetId,
|
|
targetId: companionPetId,
|
|
now: 1_000,
|
|
}).aura;
|
|
useCombatStore.setState((state) => ({
|
|
summons: state.summons.map((summon) => summon.id === calledPet.id
|
|
? { ...summon, id: playerPetId }
|
|
: summon).concat({
|
|
...calledPet,
|
|
id: companionPetId,
|
|
ownerActorId: "party:companion",
|
|
}),
|
|
auras: [...state.auras, playerProwl, companionProwl],
|
|
}));
|
|
|
|
expect(useCombatStore.getState().setHunterPetFamily("wolf")).toBe(true);
|
|
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === playerPetId
|
|
&& aura.definition.utilityTags?.includes("wow335-hunter-pet-prowl"))).toBe(false);
|
|
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === companionPetId
|
|
&& aura.definition.utilityTags?.includes("wow335-hunter-pet-prowl"))).toBe(true);
|
|
|
|
expect(useCombatStore.getState().setHunterPetFamily("cat")).toBe(true);
|
|
const refreshedPlayerProwl = applyAura([], {
|
|
definition: wow335HunterPetProwlAura(prowl),
|
|
sourceId: playerPetId,
|
|
targetId: playerPetId,
|
|
now: 2_000,
|
|
}).aura;
|
|
useCombatStore.setState((state) => ({ auras: [...state.auras, refreshedPlayerProwl] }));
|
|
expect(useCombatStore.getState().setHunterPetFamily("turtle")).toBe(true);
|
|
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === playerPetId
|
|
&& aura.definition.utilityTags?.includes("wow335-hunter-pet-prowl"))).toBe(false);
|
|
|
|
const dismiss = useCombatStore.getState().abilities.find((ability) => ability.name === "Dismiss Pet");
|
|
if (!dismiss) throw new Error("Hunter Dismiss Pet is missing from the active build.");
|
|
const turtlePet = useCombatStore.getState().summons.find((summon) => summon.id === playerPetId);
|
|
if (!turtlePet?.hunterPet?.shellShield) throw new Error("Turtle Shell Shield profile is missing.");
|
|
const shellAura = applyAura([], {
|
|
definition: {
|
|
id: "test:turtle-shell",
|
|
name: "Shell Shield test aura",
|
|
disposition: "buff",
|
|
durationMs: 12_000,
|
|
maxStacks: 1,
|
|
stackBehavior: "refresh",
|
|
utilityTags: ["wow335-hunter-pet-shell-shield"],
|
|
},
|
|
sourceId: playerPetId,
|
|
targetId: playerPetId,
|
|
now: 2_000,
|
|
}).aura;
|
|
useCombatStore.setState((state) => ({ auras: [...state.auras, shellAura] }));
|
|
const dismissResult = useCombatStore.getState().castAbility(dismiss.id, [0, 0, 0], 5_000);
|
|
expect(dismissResult.ok, dismissResult.reason).toBe(true);
|
|
useCombatStore.getState().tick(5, 10_000);
|
|
expect(useCombatStore.getState().summons.some((summon) => summon.id === playerPetId)).toBe(false);
|
|
expect(useCombatStore.getState().auras.some((aura) => aura.targetId === playerPetId
|
|
&& aura.definition.utilityTags?.some((tag) => (
|
|
tag === "wow335-hunter-pet-prowl" || tag === "wow335-hunter-pet-shell-shield"
|
|
)))).toBe(false);
|
|
});
|
|
|
|
it("selects and calls a level-10 Cat from its learned Claw, Rake, and Prowl ranks", () => {
|
|
useCombatStore.getState().initializeCharacter({
|
|
classId: "hunter",
|
|
specializationId: "hunter:marksmanship",
|
|
level: 10,
|
|
xp: 0,
|
|
hunterPetFamily: "cat",
|
|
});
|
|
|
|
expect(useCombatStore.getState().hunterPetFamily).toBe("cat");
|
|
const pet = callPet(1_000);
|
|
expect(pet).toMatchObject({
|
|
name: "Cat",
|
|
hunterPet: {
|
|
family: "cat",
|
|
abilityName: "Claw",
|
|
spellId: 16_828,
|
|
rake: { spellId: 59_881 },
|
|
prowl: { spellId: 24_450 },
|
|
},
|
|
});
|
|
expect(pet.hunterPet?.furiousHowl).toBeUndefined();
|
|
expect(pet.hunterPet?.shellShield).toBeUndefined();
|
|
});
|
|
|
|
it("applies Prowl's source movement and incoming break, then buffs the earlier auto swing", () => {
|
|
expect(useCombatStore.getState().setHunterPetFamily("cat")).toBe(true);
|
|
const pet = callPet();
|
|
useCombatStore.setState((state) => ({
|
|
summons: state.summons.map((summon) => summon.id === pet.id
|
|
? { ...summon, position: [-10, 0, 0], nextActionAt: 2_000, hunterPetProwlReadyAt: 2_000 }
|
|
: summon),
|
|
}));
|
|
|
|
useCombatStore.getState().tick(1, 2_000);
|
|
expect(useCombatStore.getState().auras).toContainEqual(expect.objectContaining({
|
|
targetId: pet.id,
|
|
definition: expect.objectContaining({
|
|
id: "wow335-hunter-pet-prowl",
|
|
modifiers: expect.arrayContaining([
|
|
expect.objectContaining({ kind: "damage", direction: "dealt", school: "physical", value: 0.5 }),
|
|
expect.objectContaining({ kind: "damage", direction: "dealt", school: "arcane", value: 0.5 }),
|
|
]),
|
|
}),
|
|
}));
|
|
expect(useCombatStore.getState().summons.find((summon) => summon.id === pet.id)?.position[0]).toBeCloseTo(-7.6);
|
|
expect(useCombatStore.getState().damageSummon(pet.id, 1)).toBe(1);
|
|
expect(useCombatStore.getState().auras.some((aura) => (
|
|
aura.targetId === pet.id && aura.definition.utilityTags?.includes("wow335-hunter-pet-prowl")
|
|
))).toBe(false);
|
|
|
|
registerCatTarget("cat-prowl-opener", [2, 0, 0]);
|
|
useCombatStore.getState().commandHunterPet(pet.id, "attack");
|
|
useCombatStore.setState((state) => ({
|
|
mobs: {
|
|
...state.mobs,
|
|
"cat-prowl-opener": { ...state.mobs["cat-prowl-opener"]!, characterShield: 100_000 },
|
|
},
|
|
summons: state.summons.map((summon) => summon.id === pet.id
|
|
? {
|
|
...summon,
|
|
position: [1, 0, 0],
|
|
nextActionAt: 3_000,
|
|
hunterPetSpecialReadyAt: 3_000,
|
|
hunterPetRakeReadyAt: 3_000,
|
|
hunterPetProwlReadyAt: 3_000,
|
|
}
|
|
: summon),
|
|
}));
|
|
useCombatStore.getState().tick(1, 3_000);
|
|
|
|
const events = useCombatStore.getState().combatEvents;
|
|
const openingAutoAttack = events.find((event) => (
|
|
event.targetActorId === "cat-prowl-opener" && event.abilityId === pet.sourceAbilityId
|
|
));
|
|
const openingRakeDamage = events.find((event) => (
|
|
event.targetActorId === "cat-prowl-opener" && event.abilityId === "wow335-hunter-pet-rake-rank-6"
|
|
));
|
|
expect(openingAutoAttack?.rawAmount).toBe(Math.round(pet.attackDamage * 1.5));
|
|
expect(openingRakeDamage?.rawAmount).toBe(Math.round(
|
|
wow335HunterPetRakeDamage(pet.hunterPet!, pet.hunterPet!.rake!),
|
|
));
|
|
expect(openingAutoAttack?.effectiveAmount).toBe(0);
|
|
expect(openingAutoAttack?.absorbed).toBeGreaterThan(0);
|
|
expect(events.indexOf(openingAutoAttack!)).toBeLessThan(events.indexOf(openingRakeDamage!));
|
|
expect(useCombatStore.getState().auras.some((aura) => (
|
|
aura.targetId === pet.id && aura.definition.utilityTags?.includes("wow335-hunter-pet-prowl")
|
|
))).toBe(false);
|
|
});
|
|
|
|
it("applies armor once to Prowl's boosted Cat auto swing", () => {
|
|
expect(useCombatStore.getState().setHunterPetFamily("cat")).toBe(true);
|
|
const pet = callPet();
|
|
registerCatTarget("cat-armored-swing", [2, 0, 0]);
|
|
const targetArmor = 100_000;
|
|
const prowl = pet.hunterPet?.prowl;
|
|
if (!prowl) throw new Error("Cat Prowl profile is missing.");
|
|
const prowlAura = applyAura([], {
|
|
definition: wow335HunterPetProwlAura(prowl),
|
|
sourceId: pet.id,
|
|
targetId: pet.id,
|
|
now: 1_000,
|
|
}).aura;
|
|
useCombatStore.setState((state) => ({
|
|
mobs: {
|
|
...state.mobs,
|
|
"cat-armored-swing": { ...state.mobs["cat-armored-swing"]!, armor: targetArmor },
|
|
},
|
|
auras: [...state.auras, prowlAura],
|
|
}));
|
|
useCombatStore.getState().commandHunterPet(pet.id, "attack");
|
|
useCombatStore.setState((state) => ({
|
|
summons: state.summons.map((summon) => summon.id === pet.id
|
|
? { ...summon, position: [1, 0, 0], nextActionAt: 2_000 }
|
|
: summon),
|
|
}));
|
|
useCombatStore.getState().tick(1, 2_000);
|
|
|
|
const autoAttack = useCombatStore.getState().combatEvents.find((event) => (
|
|
event.targetActorId === "cat-armored-swing" && event.abilityId === pet.sourceAbilityId
|
|
));
|
|
expect(autoAttack?.rawAmount).toBe(Math.round(pet.attackDamage * 1.5));
|
|
expect(autoAttack?.effectiveAmount).toBe(damageAfterArmor(
|
|
autoAttack!.rawAmount,
|
|
targetArmor,
|
|
pet.hunterPet!.baseStats.level,
|
|
));
|
|
expect(useCombatStore.getState().auras.some((aura) => (
|
|
aura.targetId === pet.id && aura.definition.utilityTags?.includes("wow335-hunter-pet-prowl")
|
|
))).toBe(false);
|
|
});
|
|
|
|
it("keeps Rake ticks pet-owned through pet death, targets only its application target, and expires after three ticks", () => {
|
|
expect(useCombatStore.getState().setHunterPetFamily("cat")).toBe(true);
|
|
const pet = callPet(1_000);
|
|
registerCatTarget("cat-rake-target", [2, 0, 0]);
|
|
registerCatTarget("cat-rake-other-target", [3, 0, 0]);
|
|
useCombatStore.getState().selectMob("cat-rake-target");
|
|
const targetArmor = 100_000;
|
|
useCombatStore.setState((state) => ({
|
|
mobs: {
|
|
...state.mobs,
|
|
"cat-rake-target": { ...state.mobs["cat-rake-target"]!, armor: targetArmor },
|
|
},
|
|
auras: [...state.auras, applyAura([], {
|
|
definition: {
|
|
id: "test:cat-rake-target-damage-taken",
|
|
name: "Test physical vulnerability",
|
|
disposition: "debuff",
|
|
durationMs: 30_000,
|
|
maxStacks: 1,
|
|
stackBehavior: "refresh",
|
|
modifiers: [{ kind: "damage", direction: "taken", school: "physical", operation: "percent", value: 0.2 }],
|
|
},
|
|
sourceId: "test-source",
|
|
targetId: "cat-rake-target",
|
|
now: 1_000,
|
|
}).aura],
|
|
}));
|
|
useCombatStore.setState((state) => ({
|
|
summons: state.summons.map((summon) => summon.id === pet.id
|
|
? { ...summon, position: [1, 0, 0], nextActionAt: 2_000, hunterPetSpecialReadyAt: 2_000, hunterPetRakeReadyAt: 2_000 }
|
|
: summon),
|
|
}));
|
|
useCombatStore.getState().commandHunterPet(pet.id, "attack");
|
|
const killCommand = useCombatStore.getState().abilities.find((ability) => ability.name === "Kill Command");
|
|
if (!killCommand) throw new Error("Hunter Kill Command is missing from the active build.");
|
|
expect(useCombatStore.getState().castAbility(killCommand.id, {
|
|
targetActorId: "cat-rake-target",
|
|
now: 3_000,
|
|
}).ok).toBe(true);
|
|
useCombatStore.setState((state) => ({
|
|
summons: state.summons.map((summon) => summon.id === pet.id
|
|
? { ...summon, nextActionAt: 4_000, hunterPetSpecialReadyAt: 4_000, hunterPetRakeReadyAt: 4_000,
|
|
hunterPetProwlReadyAt: 4_000 }
|
|
: summon),
|
|
}));
|
|
useCombatStore.getState().tick(1, 4_000);
|
|
|
|
const rake = useCombatStore.getState().effects.find((effect) => effect.tag === "wow335-hunter-pet-rake");
|
|
expect(rake).toMatchObject({
|
|
sourceActorId: pet.id,
|
|
sourceAbilityId: "wow335-hunter-pet-rake-rank-6",
|
|
targetId: "cat-rake-target",
|
|
kind: "dot",
|
|
remainingTicks: 3,
|
|
intervalMs: 3_000,
|
|
nextTickAt: 7_000,
|
|
fixedAmount: Math.round(wow335HunterPetRakeDamage(pet.hunterPet!, pet.hunterPet!.rake!, true) * 1.6),
|
|
});
|
|
const killCommandAura = useCombatStore.getState().auras.find((aura) => (
|
|
aura.targetId === pet.id && aura.definition.utilityTags?.includes("wow335-hunter-kill-command")
|
|
));
|
|
expect(killCommandAura?.stacks).toBe(2);
|
|
useCombatStore.getState().damageSummon(pet.id, pet.health);
|
|
expect(useCombatStore.getState().summons.some((summon) => summon.id === pet.id)).toBe(false);
|
|
|
|
for (const now of [7_000, 10_000, 13_000]) {
|
|
useCombatStore.getState().tick(3, now);
|
|
}
|
|
const rakeTicks = useCombatStore.getState().combatEvents.filter((event) => (
|
|
event.targetActorId === "cat-rake-target" && event.abilityId === "wow335-hunter-pet-rake-rank-6"
|
|
));
|
|
expect(rakeTicks).toHaveLength(4);
|
|
expect(rakeTicks.slice(1).map((event) => event.occurredAt)).toEqual([7_000, 10_000, 13_000]);
|
|
expect(rakeTicks[0]).toMatchObject({
|
|
rawAmount: expect.any(Number),
|
|
effectiveAmount: Math.round(damageAfterArmor(
|
|
rakeTicks[0]!.rawAmount,
|
|
targetArmor,
|
|
pet.hunterPet!.baseStats.level,
|
|
) * 1.2),
|
|
});
|
|
expect(rakeTicks[0]?.rawAmount).toBe(Math.round(
|
|
wow335HunterPetRakeDamage(pet.hunterPet!, pet.hunterPet!.rake!) * 1.6,
|
|
));
|
|
expect(rakeTicks.slice(1).every((event) => event.sourceActorId === pet.id)).toBe(true);
|
|
expect(rakeTicks.slice(1).every((event) => event.rawAmount === rake?.fixedAmount)).toBe(true);
|
|
expect(rakeTicks.slice(1).every((event) => (
|
|
event.effectiveAmount === Math.round(event.rawAmount * 1.2)
|
|
))).toBe(true);
|
|
expect(useCombatStore.getState().auras.find((aura) => aura.instanceId === killCommandAura?.instanceId)?.stacks).toBe(2);
|
|
expect(useCombatStore.getState().effects.some((effect) => effect.tag === "wow335-hunter-pet-rake")).toBe(false);
|
|
expect(useCombatStore.getState().mobs["cat-rake-target"]?.health).toBeLessThan(100_000);
|
|
expect(useCombatStore.getState().mobs["cat-rake-other-target"]?.health).toBe(100_000);
|
|
expect(useCombatStore.getState().combatEvents.some((event) => (
|
|
event.targetActorId === "cat-rake-other-target" && event.abilityId === "wow335-hunter-pet-rake-rank-6"
|
|
))).toBe(false);
|
|
});
|
|
});
|
|
|
|
function registerCatTarget(id: string, position: readonly [number, number, number]): void {
|
|
useCombatStore.getState().registerMob(id, { name: id, maxHealth: 100_000, xpReward: 0 });
|
|
setMobPosition(id, position);
|
|
useCombatStore.getState().selectMob(id);
|
|
}
|