45 lines
1.7 KiB
TypeScript
45 lines
1.7 KiB
TypeScript
import { useSyncExternalStore } from "react";
|
|
import {
|
|
activeDungeonEncounterRevision,
|
|
subscribeDungeonEncounterSnapshots,
|
|
} from "../game/dungeonEncounterRuntime";
|
|
import {
|
|
activeDungeonInteractionCandidate,
|
|
interactWithActiveDungeonObject,
|
|
} from "../game/dungeonWorldInteraction";
|
|
import { useGameStore } from "../game/store";
|
|
import { useInputBindingLabels } from "../input/useInputBindingLabels";
|
|
|
|
export function DungeonInteractionPrompt({ interaction, onInteract }: {
|
|
interaction?: { prompt: string; name: string; label: string } | null;
|
|
onInteract?: () => void;
|
|
} = {}) {
|
|
const label = useInputBindingLabels();
|
|
const playerPosition = useGameStore((state) => state.playerPosition);
|
|
const inputMode = useGameStore((state) => state.inputMode);
|
|
const overlay = useGameStore((state) => state.overlay);
|
|
useSyncExternalStore(
|
|
subscribeDungeonEncounterSnapshots,
|
|
activeDungeonEncounterRevision,
|
|
activeDungeonEncounterRevision,
|
|
);
|
|
void playerPosition;
|
|
const local = interaction === undefined && !overlay ? activeDungeonInteractionCandidate() : null;
|
|
const candidate = interaction ?? (local ? { prompt: local.object.interaction!.prompt, name: local.object.name, label: local.state.label } : null);
|
|
if (!candidate) return null;
|
|
return (
|
|
<button
|
|
type="button"
|
|
className="dungeon-interaction-prompt"
|
|
onClick={onInteract ?? (() => interactWithActiveDungeonObject())}
|
|
aria-label={`${candidate.prompt}: ${candidate.name}`}
|
|
>
|
|
<span className="dungeon-interaction-prompt__key">{label(inputMode === "gamepad" ? "controller" : "pc", "loot")}</span>
|
|
<span>
|
|
<strong>{candidate.prompt}</strong>
|
|
<small>{candidate.name} · {candidate.label}</small>
|
|
</span>
|
|
</button>
|
|
);
|
|
}
|