forked from phenom/healer-man
99 lines
4.0 KiB
TypeScript
99 lines
4.0 KiB
TypeScript
import { useFrame } from "@react-three/fiber";
|
|
import { useRef, useSyncExternalStore } from "react";
|
|
import type { Group } from "three";
|
|
import { BITE_SIZED_WAILING_CAVERNS_DUNGEON_ID } from "../game/dungeonContentSource";
|
|
import {
|
|
dispatchDungeonEncounterSignal,
|
|
dungeonEncounterSnapshot,
|
|
subscribeDungeonEncounterSnapshots,
|
|
} from "../game/dungeonEncounterRuntime";
|
|
import { useGameStore } from "../game/store";
|
|
import {
|
|
useWailingEncounterStore,
|
|
wailingDisciplePosition,
|
|
type WailingEncounterPhase,
|
|
} from "../game/wailingCavernsEncounter";
|
|
import { WorldNpcPrompt } from "./WorldNpcPrompt";
|
|
import { dungeonSimulationNow } from "../game/dungeonSimulationStore";
|
|
|
|
export function WailingNaralexEvent() {
|
|
const activeDungeonId = useGameStore((state) => state.activeDungeonId);
|
|
const gameMode = useGameStore((state) => state.gameMode);
|
|
const sourcePhase = useWailingEncounterStore((state) => state.phase);
|
|
const sourcePhaseStartedAt = useWailingEncounterStore((state) => state.phaseStartedAt);
|
|
const biteEncounterId = `${BITE_SIZED_WAILING_CAVERNS_DUNGEON_ID}:naralex-escort`;
|
|
useSyncExternalStore(
|
|
subscribeDungeonEncounterSnapshots,
|
|
() => dungeonEncounterSnapshot(biteEncounterId)?.revision ?? 0,
|
|
() => 0,
|
|
);
|
|
const biteSnapshot = dungeonEncounterSnapshot(biteEncounterId);
|
|
const phase = activeDungeonId === BITE_SIZED_WAILING_CAVERNS_DUNGEON_ID
|
|
? biteSnapshot?.phase ?? "ready"
|
|
: sourcePhase;
|
|
const phaseStartedAt = activeDungeonId === BITE_SIZED_WAILING_CAVERNS_DUNGEON_ID
|
|
? biteSnapshot?.phaseStartedAt ?? 0
|
|
: sourcePhaseStartedAt;
|
|
const isBiteSized = activeDungeonId === BITE_SIZED_WAILING_CAVERNS_DUNGEON_ID;
|
|
const groupRef = useRef<Group>(null);
|
|
|
|
useFrame(() => {
|
|
if (
|
|
(activeDungeonId !== "wailing-caverns" && !isBiteSized)
|
|
|| gameMode !== "dungeon"
|
|
) return;
|
|
const liveSnapshot = isBiteSized ? dungeonEncounterSnapshot(biteEncounterId) : null;
|
|
const now = dungeonSimulationNow();
|
|
const position = wailingDisciplePosition(
|
|
(liveSnapshot?.phase ?? useWailingEncounterStore.getState().phase) as Parameters<typeof wailingDisciplePosition>[0],
|
|
liveSnapshot?.phaseStartedAt ?? useWailingEncounterStore.getState().phaseStartedAt,
|
|
now,
|
|
);
|
|
groupRef.current?.position.set(...position);
|
|
});
|
|
|
|
if (
|
|
(activeDungeonId !== "wailing-caverns" && !isBiteSized)
|
|
|| gameMode !== "dungeon"
|
|
) return null;
|
|
const initialPosition = wailingDisciplePosition(phase as WailingEncounterPhase, phaseStartedAt);
|
|
return (
|
|
<group ref={groupRef} name="disciple-of-naralex-event" position={initialPosition}>
|
|
<mesh position={[0, 0.9, 0]} castShadow>
|
|
<capsuleGeometry args={[0.34, 1.15, 6, 10]} />
|
|
<meshStandardMaterial color="#526d3d" roughness={0.76} />
|
|
</mesh>
|
|
<mesh position={[0, 1.72, 0]} castShadow>
|
|
<sphereGeometry args={[0.3, 12, 8]} />
|
|
<meshStandardMaterial color="#b9d67a" roughness={0.7} />
|
|
</mesh>
|
|
<WorldNpcPrompt position={[0, 2.45, 0]}>
|
|
<div className="naralex-event-prompt">
|
|
<strong>Disciple of Naralex</strong>
|
|
{phase === "locked" ? <small>Defeat the four Fanglords</small> : null}
|
|
{phase === "ready" ? (
|
|
<button
|
|
type="button"
|
|
onClick={(event) => {
|
|
event.stopPropagation();
|
|
if (isBiteSized) {
|
|
dispatchDungeonEncounterSignal(
|
|
`${BITE_SIZED_WAILING_CAVERNS_DUNGEON_ID}:naralex-start`,
|
|
dungeonSimulationNow(),
|
|
);
|
|
} else {
|
|
useWailingEncounterStore.getState().start();
|
|
}
|
|
}}
|
|
>
|
|
Begin the awakening
|
|
</button>
|
|
) : null}
|
|
{phase === "cleansing" ? <small>Channeling Serpentine Cleansing…</small> : null}
|
|
{phase === "complete" ? <small>{isBiteSized ? "Naralex has been rescued" : "Naralex has awakened"}</small> : null}
|
|
</div>
|
|
</WorldNpcPrompt>
|
|
</group>
|
|
);
|
|
}
|