Files
healer-man/src/ui/RomSkillsPanel.tsx
2026-09-23 16:14:19 -04:00

55 lines
3.9 KiB
TypeScript

import { useMemo } from "react";
import { classById } from "../app/characterCatalog";
import { abilityAtLevel, type AbilityDefinition } from "../game/abilityCatalog";
import { useCombatStore } from "../game/combatStore";
import { useGameStore } from "../game/store";
import { useMenuController, type MenuAction } from "../input/useMenuController";
import { ControllerButton } from "./ControllerButton";
const GROUPS = [
{ id: "primary", label: "Primary" },
{ id: "general", label: "Secondary General" },
{ id: "elite", label: "Elite" },
{ id: "passive", label: "Passive" },
] as const;
function visibleGroup(ability: AbilityDefinition, primaryClassId: string): (typeof GROUPS)[number]["id"] {
if (ability.passive) return "passive";
if (ability.romSkillGroup === "elite") return "elite";
if (ability.classId !== primaryClassId) return "general";
return "primary";
}
export function RomSkillsPanel({ open, onClose }: { open?: boolean; onClose?: () => void } = {}) {
const storeOpen = useGameStore((state) => state.overlay === "talents");
const closeOverlay = useGameStore((state) => state.closeOverlay);
const close = onClose ?? closeOverlay;
const classId = useCombatStore((state) => state.classId);
const secondaryClassId = useCombatStore((state) => state.secondaryClassId);
const level = useCombatStore((state) => state.level);
const abilities = useCombatStore((state) => state.abilities);
const actions = useMemo<MenuAction[]>(() => [
...abilities.map((ability) => ({ id: `rom-skill-${ability.id}`, run: () => undefined })),
{ id: "rom-skills-close", run: close },
], [abilities, close]);
const controller = useMenuController(actions, { initialId: abilities[0] ? `rom-skill-${abilities[0].id}` : "rom-skills-close", onBack: close });
if (!(open ?? storeOpen)) return null;
const primary = classById(classId);
const secondary = secondaryClassId ? classById(secondaryClassId) : null;
return (
<div className="modal-backdrop game-panel-backdrop" role="presentation">
<section className="game-panel rom-skills-panel" role="dialog" aria-modal="true" aria-labelledby="rom-skills-title">
<header className="game-panel__header"><div><p>Runes of Magic / Automatic progression</p><h2 id="rom-skills-title">Skills</h2></div><ControllerButton controlId="rom-skills-close" selectedId={controller.selectedId} select={controller.select} type="button" onClick={close}>Close</ControllerButton></header>
<p className="rom-skills-panel__intro"><strong>{primary.name}{secondary ? ` / ${secondary.name}` : ""}</strong><span>Skills unlock automatically as you level. There are no talent points or spell ranks to assign.</span></p>
<div className="rom-skills-groups">
{GROUPS.filter((group) => abilities.some((ability) => visibleGroup(ability, classId) === group.id)).map((group) => {
const entries = abilities.filter((ability) => visibleGroup(ability, classId) === group.id);
return <section key={group.id}><header><h3>{group.label}</h3><span>{entries.filter((ability) => level >= ability.unlockLevel).length} / {entries.length}</span></header><div>{entries.map((catalogAbility) => { const ability = abilityAtLevel(catalogAbility, level); const locked = level < catalogAbility.unlockLevel; return <ControllerButton key={ability.id} controlId={`rom-skill-${ability.id}`} selectedId={controller.selectedId} select={controller.select} className={`rom-skill-card ${locked ? "is-locked" : ""}`} type="button"><img src={ability.icon} alt=""/><span><strong>{ability.name}</strong><small>{locked ? `Unlocks at level ${ability.unlockLevel}` : ability.secondaryClassId ? `Elite / ${secondary?.name ?? "Secondary class"}` : "Learned"}</small></span><em>{ability.passive ? "Passive" : ability.cost.amount ? `${ability.cost.amount} ${ability.cost.resource}` : "No cost"}</em></ControllerButton>; })}</div></section>;
})}
</div>
</section>
</div>
);
}