Files
healer-man/src/app/gmManastormRuntime.ts
T
2026-09-01 14:35:56 -04:00

156 lines
6.3 KiB
TypeScript

import { touchCharacter } from "./accountRepository";
import { useShellStore } from "./shellStore";
import { registerGeneratedManastormStageAssets } from "../game/manastormAssetRegistry";
import {
isGameMasterSession,
resolvedBossAdminCatalog,
} from "../game/manastormAdminConfig";
import {
drawRandomGmReviewTarget,
type GmReviewKind,
type GmReviewTarget,
} from "../game/gmContentReview";
import { useManastormAdminStore } from "../game/manastormAdminStore";
import { INSTALLED_MANASTORM_RUNTIME_CATALOG } from "../game/manastormInstalledCatalog";
import { installedManastormStageBinding } from "../game/manastormInstalledStages";
import { registerManastormStageBindingResolver } from "../game/manastormStagePopulation";
import { activateManastormStage, releaseActiveManastormStage } from "../game/manastormStageLoader";
import { manastormModeForCharacterLevel, setManastormCatalog } from "../game/manastorm";
import { createEmptyManastormProgress, manastormModeProgress } from "../game/manastormProgress";
import { manastormActiveSpellLoadoutModel } from "../game/manastormSpellLoadout";
import { useManastormStore } from "../game/manastormStore";
import { defaultPartyRoleForClass } from "../game/partyRoles";
import { useGameStore } from "../game/store";
import { activateDungeonSession } from "../game/dungeonSession";
import type { CharacterProfile } from "./types";
function activeGmCharacter(): CharacterProfile | null {
const shell = useShellStore.getState();
if (!isGameMasterSession(shell.session) || !shell.selectedCharacterId) {
useShellStore.setState({ notice: "Game Master access is required." });
return null;
}
const activeCharacter = touchCharacter(shell.session!.ownerId, shell.selectedCharacterId)
?? shell.characters.find((character) => character.id === shell.selectedCharacterId)
?? null;
if (!activeCharacter) {
useShellStore.setState({ notice: "Select a character before testing content." });
return null;
}
return activeCharacter;
}
function enterGmDungeonTest(target: Extract<GmReviewTarget, { readonly kind: "dungeon" }>): boolean {
const activeCharacter = activeGmCharacter();
if (!activeCharacter) return false;
useManastormStore.getState().resetRun();
releaseActiveManastormStage();
if (!activateDungeonSession(target.dungeonId, undefined, {
gmTestMode: true,
gmReviewTarget: target,
})) {
useShellStore.setState({ notice: "That dungeon did not pass its offline runtime gate." });
return false;
}
useGameStore.getState().setCompanionOpen(false);
useShellStore.setState({
phase: "game",
activeCharacter,
activeDungeonId: target.dungeonId,
activeDungeonRole: defaultPartyRoleForClass(activeCharacter.classId),
notice: "GM dungeon review",
});
return true;
}
export function enterRandomGmReview(kind: GmReviewKind): boolean {
const admin = useManastormAdminStore.getState();
if (admin.dirty || admin.saving) {
useShellStore.setState({ notice: "Save or discard GM changes before testing another target." });
return false;
}
const previous = useGameStore.getState().activeGmReviewTarget;
const target = drawRandomGmReviewTarget(kind, admin.config, previous);
if (!target) {
const label = kind === "dungeon" ? "dungeons" : kind === "boss" ? "bosses" : "Manastorm encounters";
useShellStore.setState({ notice: `All runnable ${label} have been marked Ready.` });
return false;
}
if (target.kind === "dungeon") return enterGmDungeonTest(target);
return enterGmManastormTest(target.bossKey, target);
}
export function enterGmManastormTest(bossKey: string, reviewTarget: GmReviewTarget | null = null): boolean {
const activeCharacter = activeGmCharacter();
if (!activeCharacter) return false;
const adminConfig = useManastormAdminStore.getState().config;
const entry = resolvedBossAdminCatalog(adminConfig).find((candidate) => candidate.key === bossKey);
if (!entry || !entry.runnable || !entry.stage) {
useShellStore.setState({ notice: entry?.validationIssues[0] ?? "That boss is not available for GM testing." });
return false;
}
registerGeneratedManastormStageAssets();
registerManastormStageBindingResolver(installedManastormStageBinding);
const modeId = manastormModeForCharacterLevel(activeCharacter.level, INSTALLED_MANASTORM_RUNTIME_CATALOG);
const { milestoneLevel: _milestoneLevel, ...stageWithoutMilestone } = entry.stage;
const testStage = Object.freeze({
...stageWithoutMilestone,
modeIds: [modeId],
stageKind: "standard" as const,
unlockLevel: 1,
chaoticLinkEnabled: adminConfig.global.chaoticLinkEnabled,
});
const testCatalog = Object.freeze({
...INSTALLED_MANASTORM_RUNTIME_CATALOG,
stages: [testStage],
fallbackPolicy: "none" as const,
runtimeOptions: Object.freeze({ ...adminConfig.global }),
});
setManastormCatalog(testCatalog);
useManastormStore.getState().resetRun();
const progress = activeCharacter.manastormProgress ?? createEmptyManastormProgress();
const modeProgress = manastormModeProgress(progress, modeId);
const spellLoadout = manastormActiveSpellLoadoutModel(
activeCharacter.classId,
activeCharacter.level,
activeCharacter.talentRanks,
);
let encounter;
try {
encounter = useManastormStore.getState().startRun({
partySize: 5,
startingLevel: 1,
unlockedThroughLevel: 1,
modeId,
activeSpellLoadout: {
available: spellLoadout.options,
selectedIds: progress.loadout,
maxSelections: spellLoadout.maxSelections,
provenance: spellLoadout.provenance,
},
cacheMeter: modeProgress.cacheMeter,
rewardCaches: modeProgress.rewardCaches,
random: () => 0,
});
} catch {
useShellStore.setState({ notice: "The configured boss could not be prepared for testing." });
return false;
}
if (!activateManastormStage(encounter, undefined, false, true, reviewTarget).activated) {
useManastormStore.getState().resetRun();
useShellStore.setState({ notice: "The configured dungeon or stage package is not loadable." });
return false;
}
useGameStore.getState().setCompanionOpen(false);
useShellStore.setState({
phase: "game",
activeCharacter,
activeDungeonId: encounter.dungeonId,
activeDungeonRole: defaultPartyRoleForClass(activeCharacter.classId),
notice: `GM test: ${entry.name}`,
});
return true;
}