forked from phenom/healer-man
67 lines
3.4 KiB
TypeScript
67 lines
3.4 KiB
TypeScript
import { useEffect } from "react";
|
|
import { useShellStore } from "../app/shellStore";
|
|
import { useOnlineSessionStore } from "../app/onlineSessionStore";
|
|
import { useOnlineGroupStore } from "../app/onlineGroupStore";
|
|
import { activeDungeonCompletedObjectiveIds, subscribeDungeonEncounterSnapshots } from "./dungeonEncounterRuntime";
|
|
import { useCombatStore } from "./combatStore";
|
|
import { usePartyStore } from "./partyStore";
|
|
import { useGameStore } from "./store";
|
|
import { useDungeonRunStore } from "./dungeonRunStore";
|
|
import type { DungeonRunMember } from "./dungeonRunTypes";
|
|
|
|
function currentRunParty(): readonly DungeonRunMember[] {
|
|
const shell = useShellStore.getState();
|
|
const character = shell.activeCharacter;
|
|
if (!character) return [];
|
|
const members: DungeonRunMember[] = [{ name: character.name, classId: character.classId,
|
|
level: useCombatStore.getState().level, role: shell.activeDungeonRole, npc: false,
|
|
...(character.secondaryClassId ? { secondaryClassId: character.secondaryClassId } : {}) }];
|
|
const online = useOnlineSessionStore.getState().snapshot;
|
|
for (const player of online?.players ?? []) {
|
|
if (player.accountId === shell.session?.ownerId || !player.character) continue;
|
|
members.push({ name: player.character.name, classId: player.character.classId,
|
|
level: player.character.level, role: player.role ?? "damage", npc: false });
|
|
}
|
|
for (const member of usePartyStore.getState().members) {
|
|
members.push({ name: member.name, classId: member.classId, level: member.level, role: member.role, npc: true });
|
|
}
|
|
return members.slice(0, 5);
|
|
}
|
|
|
|
/** Wall-clock run timing survives wipes, pauses, checkpoints, and renderer retries. */
|
|
export function DungeonRunBridge() {
|
|
useEffect(() => {
|
|
let announcedId: string | null = null;
|
|
const advance = () => {
|
|
const game = useGameStore.getState();
|
|
const shell = useShellStore.getState();
|
|
if (shell.phase !== "game" || game.gameMode !== "dungeon" || game.gmTestMode
|
|
|| !["ready", "proxy"].includes(game.assetStatus)) return;
|
|
const run = useDungeonRunStore.getState();
|
|
if (run.progress?.dungeonId !== game.activeDungeonId) return;
|
|
const party = currentRunParty();
|
|
const activity = useOnlineGroupStore.getState().activeActivity;
|
|
if (activity?.type === "dungeon" && activity.contentId === game.activeDungeonId
|
|
&& run.progress.finishedAt === null && run.progress.id !== activity.id) {
|
|
useDungeonRunStore.setState({ progress: { ...run.progress, id: activity.id,
|
|
startedAt: activity.startedAt, party } });
|
|
}
|
|
run.start(Date.now(), party);
|
|
run.advance(activeDungeonCompletedObjectiveIds(game.activeDungeonId),
|
|
Object.values(useCombatStore.getState().mobs).filter((mob) => mob.dead && !mob.despawned).map((mob) => mob.id),
|
|
Date.now(), party);
|
|
const progress = useDungeonRunStore.getState().progress;
|
|
if (progress?.finishedAt !== null && progress && announcedId !== progress.id) {
|
|
announcedId = progress.id;
|
|
useGameStore.getState().openOverlay("dungeon-results");
|
|
}
|
|
};
|
|
const unsubscribeEncounters = subscribeDungeonEncounterSnapshots(advance);
|
|
const unsubscribeGame = useGameStore.subscribe(advance);
|
|
const interval = setInterval(advance, 250);
|
|
advance();
|
|
return () => { unsubscribeEncounters(); unsubscribeGame(); clearInterval(interval); };
|
|
}, []);
|
|
return null;
|
|
}
|