forked from phenom/healer-man
137 lines
6.0 KiB
TypeScript
137 lines
6.0 KiB
TypeScript
import { lazy, Suspense, useMemo } from "react";
|
||
import { classById } from "../app/characterCatalog";
|
||
import { useShellStore } from "../app/shellStore";
|
||
import { useCombatStore } from "../game/combatStore";
|
||
import {
|
||
abilityAtLevel,
|
||
abilityRankForLevel,
|
||
abilityTalentNodeId,
|
||
learnedAbilityRank,
|
||
} from "../game/abilityCatalog";
|
||
import { useGameStore } from "../game/store";
|
||
import { OptionsPanel } from "./OptionsPanel";
|
||
import { AbilityBindingsPanel } from "./AbilityBindingsPanel";
|
||
import { SpellbookPanel } from "./SpellbookPanel";
|
||
import { EquipmentPanel } from "./EquipmentPanel";
|
||
import { RomSkillsPanel } from "./RomSkillsPanel";
|
||
import { getLoadedTalentGameplayPanel, loadTalentGameplayPanel } from "./talentGameplayResources";
|
||
import {
|
||
getLoadedBossGuidePanel,
|
||
getLoadedPartyManagementPanel,
|
||
loadBossGuidePanel,
|
||
loadPartyManagementPanel,
|
||
} from "./gameplayResources";
|
||
|
||
const PartyManagementPanel = lazy(() => loadPartyManagementPanel().then((module) => ({ default: module.PartyManagementPanel })));
|
||
const BossGuidePanel = lazy(() => loadBossGuidePanel().then((module) => ({ default: module.BossGuidePanel })));
|
||
|
||
const TalentGameplayPanel = lazy(loadTalentGameplayPanel);
|
||
const BossCardCollectionScreen = lazy(() => import("./BossCardCollection"));
|
||
|
||
function TalentsPanelLoading() {
|
||
const openOverlay = useGameStore((state) => state.openOverlay);
|
||
return (
|
||
<div className="modal-backdrop game-panel-backdrop" role="presentation">
|
||
<section className="game-panel talents-panel talents-panel--loading" role="dialog" aria-modal="true" aria-labelledby="talents-loading-title">
|
||
<header className="game-panel__header">
|
||
<div>
|
||
<p className="eyebrow">Specialization</p>
|
||
<h2 id="talents-loading-title">Talents</h2>
|
||
</div>
|
||
<button type="button" className="icon-button" onClick={() => openOverlay("pause")} aria-label="Return to pause menu">×</button>
|
||
</header>
|
||
<p className="game-panel__empty" role="status">Preparing talent trees…</p>
|
||
</section>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/** Maps the data-driven combat catalogs into the full-screen game panels. */
|
||
export function GameplayPanels() {
|
||
const classId = useCombatStore((state) => state.classId);
|
||
const level = useCombatStore((state) => state.level);
|
||
const abilities = useCombatStore((state) => state.abilities);
|
||
const talentRanks = useCombatStore((state) => state.talentRanks);
|
||
const settings = useCombatStore((state) => state.settings);
|
||
const presentationSettings = useShellStore((state) => state.combatPresentationSettings);
|
||
const overlay = useGameStore((state) => state.overlay);
|
||
const classDefinition = classById(classId);
|
||
const PreloadedTalentGameplayPanel = getLoadedTalentGameplayPanel();
|
||
const PreloadedPartyManagementPanel = getLoadedPartyManagementPanel();
|
||
const PreloadedBossGuidePanel = getLoadedBossGuidePanel();
|
||
|
||
const spellbookAbilities = useMemo(() => [...abilities]
|
||
.sort((left, right) => left.unlockLevel - right.unlockLevel)
|
||
.map((catalogAbility) => {
|
||
const learnedTalentRank = learnedAbilityRank(catalogAbility, talentRanks);
|
||
const ability = abilityAtLevel(catalogAbility, level, learnedTalentRank);
|
||
const activeRank = abilityRankForLevel(catalogAbility, level, learnedTalentRank);
|
||
return {
|
||
id: ability.id,
|
||
name: ability.name,
|
||
description: ability.description,
|
||
level: ability.unlockLevel,
|
||
color: classDefinition.color,
|
||
sigil: ability.sigil,
|
||
iconUrl: ability.icon,
|
||
school: classDefinition.name,
|
||
resourceCost: ability.cost.amount,
|
||
resourceLabel: useCombatStore.getState().resourceName,
|
||
cooldownSeconds: ability.cooldownMs / 1_000,
|
||
castSeconds: ability.castTimeMs / 1_000,
|
||
range: ability.range.max,
|
||
known: !abilityTalentNodeId(catalogAbility) || Boolean(learnedTalentRank),
|
||
talentLocked: Boolean(abilityTalentNodeId(catalogAbility)),
|
||
passive: ability.passive,
|
||
rank: ability.source === "runewaker" ? undefined : activeRank?.rank,
|
||
rankCount: ability.source === "runewaker" ? undefined : catalogAbility.ranks?.length,
|
||
ranks: ability.source === "runewaker" ? undefined : catalogAbility.ranks?.map((rank) => ({
|
||
rank: rank.rank,
|
||
level: rank.level,
|
||
spellId: rank.spellId,
|
||
description: rank.description,
|
||
})),
|
||
};
|
||
}), [abilities, classDefinition.color, classDefinition.name, level, talentRanks]);
|
||
|
||
const castAbility = (abilityId: string) => {
|
||
const game = useGameStore.getState();
|
||
game.closeOverlay();
|
||
useCombatStore.getState().castAbility(abilityId, game.playerPosition, Date.now());
|
||
};
|
||
|
||
return (
|
||
<>
|
||
{overlay === "boss-cards" ? <Suspense fallback={null}><BossCardCollectionScreen /></Suspense> : null}
|
||
{overlay === "party"
|
||
? PreloadedPartyManagementPanel
|
||
? <PreloadedPartyManagementPanel />
|
||
: <Suspense fallback={null}><PartyManagementPanel /></Suspense>
|
||
: null}
|
||
{overlay === "boss-guide"
|
||
? PreloadedBossGuidePanel
|
||
? <PreloadedBossGuidePanel />
|
||
: <Suspense fallback={null}><BossGuidePanel /></Suspense>
|
||
: null}
|
||
<EquipmentPanel />
|
||
<AbilityBindingsPanel />
|
||
<SpellbookPanel abilities={spellbookAbilities} playerLevel={level} onActivate={castAbility} />
|
||
{overlay === "talents"
|
||
? classId.startsWith("rom-")
|
||
? <RomSkillsPanel />
|
||
: PreloadedTalentGameplayPanel
|
||
? <PreloadedTalentGameplayPanel />
|
||
: <Suspense fallback={<TalentsPanelLoading />}><TalentGameplayPanel /></Suspense>
|
||
: null}
|
||
<OptionsPanel
|
||
settings={settings}
|
||
onChange={(patch) => useCombatStore.getState().updateSettings(patch)}
|
||
onReset={() => useCombatStore.getState().resetSettings()}
|
||
presentationSettings={presentationSettings}
|
||
onPresentationChange={(patch) => useShellStore.getState().updateCombatPresentationSettings(patch)}
|
||
onPresentationReset={() => useShellStore.getState().resetCombatPresentationSettings()}
|
||
/>
|
||
</>
|
||
);
|
||
}
|