Release Healer Man 0.1.22

This commit is contained in:
phenom committed 2026-09-09 11:21:33 -04:00
1 parent f6f2375145
commit 7f6001723a
144 files changed
+54107 -690

No files matched your search

+113 -6
View File
@@ -4,8 +4,13 @@ import { installManastormShellRuntime } from "../app/manastormShellRuntime";
import { selectedCharacter, useShellStore } from "../app/shellStore";
import { preloadAvatarModels } from "../avatar/avatarPreloader";
import { DualDisplayFrame } from "../components/DualDisplayFrame";
import { contentPackIsUsable, installContentPack } from "../content/contentManager";
import { useContentManagerSnapshot } from "../content/useContentManager";
import { MANASTORM_CATALOG_SUMMARY } from "../game/manastormCatalogSummary";
import { GENERATED_MANASTORM_STAGE_ASSET_PACKAGE_COUNT } from "../game/manastormAssetRegistry";
import {
GENERATED_MANASTORM_STAGE_ASSET_PACKAGE_COUNT,
generatedManastormContentPackId,
} from "../game/manastormAssetRegistry";
import { INSTALLED_MANASTORM_RUNTIME_CATALOG } from "../game/manastormInstalledCatalog";
import { effectiveManastormCatalog } from "../game/manastormAdminConfig";
import { useManastormAdminStore } from "../game/manastormAdminStore";
@@ -46,6 +51,7 @@ const ROLE_DETAILS: Readonly<Record<PartyRole, { readonly sigil: string; readonl
};
export function ManastormSelectScreen() {
const contentState = useContentManagerSnapshot();
const activeCharacter = useShellStore((state) => state.activeCharacter);
const rosterCharacter = useShellStore(selectedCharacter);
const enterManastorm = useShellStore((state) => state.enterManastorm);
@@ -80,9 +86,20 @@ export function ManastormSelectScreen() {
);
const modeEnabled = adminConfig.global.enabled
&& (modeId === 2 ? adminConfig.global.endgameEnabled : adminConfig.global.levelingEnabled);
const hasReadyBosses = Boolean(effectiveCatalog.stages?.some((stage) => (
const eligibleStages = useMemo(() => effectiveCatalog.stages?.filter((stage) => (
!stage.modeIds?.length || stage.modeIds.includes(modeId)
)));
)) ?? [], [effectiveCatalog, modeId]);
const hasReadyBosses = eligibleStages.length > 0;
const requiredContentPackIds = useMemo(() => Object.freeze([...new Set(eligibleStages.flatMap((stage) => {
if (stage.assetPackageId) {
const packId = generatedManastormContentPackId(stage.assetPackageId);
return packId ? [packId] : [];
}
return stage.dungeonId ? [`dungeon:${stage.dungeonId}`] : [];
}))].sort()), [eligibleStages]);
const missingContentPackIds = import.meta.env.DEV
? []
: requiredContentPackIds.filter((packId) => !contentPackIsUsable(packId));
const canEnter = modeEnabled && hasReadyBosses;
const modeProgress = manastormModeProgress(progress, modeId);
const modeLabel = modeId === 2 ? "End-game" : "Leveling";
@@ -124,6 +141,9 @@ export function ManastormSelectScreen() {
);
const persistedSpellIds = progress.loadout.filter((id) => availableSpellIds.has(id));
const [selectedSpellIds, setSelectedSpellIds] = useState<readonly string[]>(persistedSpellIds);
const [pendingDownloadRole, setPendingDownloadRole] = useState<PartyRole | null>(null);
const [downloading, setDownloading] = useState(false);
const [downloadError, setDownloadError] = useState("");
useEffect(() => {
setSelectedSpellIds(persistedSpellIds);
@@ -155,7 +175,7 @@ export function ManastormSelectScreen() {
});
};
const enterAsRole = async (role: PartyRole) => {
const continueAsRole = async (role: PartyRole) => {
if (!canEnter || !character) return;
if (!onlineGroup || !session || session.kind !== "account") {
enterManastorm(role, partySize, startingLevel);
@@ -174,7 +194,42 @@ export function ManastormSelectScreen() {
});
};
const actions = useMemo<MenuAction[]>(() => [
const enterAsRole = async (role: PartyRole) => {
if (!canEnter || !character) return;
if (missingContentPackIds.length > 0) {
setDownloadError("");
setPendingDownloadRole(role);
return;
}
await continueAsRole(role);
};
const confirmDownload = async () => {
if (!pendingDownloadRole || downloading) return;
setDownloading(true);
setDownloadError("");
try {
for (const packId of requiredContentPackIds) {
if (!contentPackIsUsable(packId)) await installContentPack(packId);
}
const stillMissing = requiredContentPackIds.filter((packId) => !contentPackIsUsable(packId));
if (stillMissing.length > 0) {
throw new Error("One or more downloaded Manastorm maps could not be verified on this device.");
}
const role = pendingDownloadRole;
setPendingDownloadRole(null);
await continueAsRole(role);
} catch (error) {
setDownloadError(error instanceof Error ? error.message : "The Manastorm download failed.");
} finally {
setDownloading(false);
}
};
const actions = useMemo<MenuAction[]>(() => pendingDownloadRole ? [
{ id: "manastorm-download-cancel", run: () => setPendingDownloadRole(null), enabled: !downloading },
{ id: "manastorm-download-confirm", run: () => void confirmDownload(), enabled: !downloading },
] : [
...MANASTORM_PARTY_SIZES.map((size) => ({
id: `manastorm-party-${size}`,
run: () => setPartySize(size),
@@ -196,10 +251,13 @@ export function ManastormSelectScreen() {
availableRoles,
canEnter,
checkpoint,
contentState.installedRevision,
groupHumanCount,
fillWithAi,
onlineGroup,
effectivePartySize,
downloading,
pendingDownloadRole,
returnToMainMenu,
selectedSpellIds,
spellLoadoutModel,
@@ -207,7 +265,9 @@ export function ManastormSelectScreen() {
]);
const controller = useMenuController(actions, {
initialId: "manastorm-party-5",
onBack: returnToMainMenu,
onBack: pendingDownloadRole
? () => { if (!downloading) setPendingDownloadRole(null); }
: returnToMainMenu,
});
const race = character ? raceById(character.raceId) : null;
@@ -246,6 +306,11 @@ export function ManastormSelectScreen() {
: "No bosses are currently marked Ready for this Manastorm mode."}
</p>
) : null}
{canEnter && missingContentPackIds.length > 0 ? (
<p className="front-notice" role="status">
{missingContentPackIds.length} Manastorm map {missingContentPackIds.length === 1 ? "pack is" : "packs are"} not on this device. Choose a role to download them and continue.
</p>
) : null}
<dl>
<div><dt>Catalog maps</dt><dd>{MANASTORM_CATALOG_SUMMARY.maps}</dd></div>
<div><dt>Streamed maps</dt><dd>{GENERATED_MANASTORM_STAGE_ASSET_PACKAGE_COUNT}</dd></div>
@@ -412,6 +477,48 @@ export function ManastormSelectScreen() {
</section>
</main>
<ControllerLegend back />
{pendingDownloadRole && (
<div className="front-modal-backdrop">
<section className="front-confirm" role="alertdialog" aria-modal="true" aria-labelledby="manastorm-download-title">
<p className="front-kicker">Download required</p>
<h2 id="manastorm-download-title">Download {modeLabel} Manastorm maps?</h2>
<p>
This mode can draw {missingContentPackIds.length} map {missingContentPackIds.length === 1 ? "pack" : "packs"} that {missingContentPackIds.length === 1 ? "is" : "are"} not on this device. Download and verify the required files now, then enter the storm?
</p>
{downloading && (
<p role="status">
Downloading and verifying files
{contentState.totalBytes > 0 ? ` — ${Math.round((contentState.downloadedBytes / contentState.totalBytes) * 100)}%` : "..."}
</p>
)}
{downloadError && <p className="front-notice" role="alert">{downloadError}</p>}
<div>
<ControllerButton
controlId="manastorm-download-cancel"
selectedId={controller.selectedId}
select={controller.select}
className="front-button"
type="button"
disabled={downloading}
onClick={() => setPendingDownloadRole(null)}
>
Not now
</ControllerButton>
<ControllerButton
controlId="manastorm-download-confirm"
selectedId={controller.selectedId}
select={controller.select}
className="front-button front-button--primary"
type="button"
disabled={downloading}
onClick={() => void confirmDownload()}
>
{downloading ? "Downloading..." : downloadError ? "Try again" : "Download and continue"}
</ControllerButton>
</div>
</section>
</div>
)}
</FrontSurface>
}
bottom={<LazyLocalCompanionPanel />}