Release Healer Man 0.1.14

This commit is contained in:
phenom committed 2026-09-05 11:40:50 -04:00
1 parent 67d7a8ebe7
commit 7b41b102fa
147 files changed
+169892 -1342

No files matched your search

+81 -6
View File
@@ -1,4 +1,4 @@
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { useGameStore } from "../game/store";
import {
DEFAULT_THREAT_METER_POSITION,
@@ -20,6 +20,11 @@ import {
type GameplayFrameRateLimit,
type PreviewFrameRateLimit,
} from "../game/frameRateSettings";
import {
combatMediaPackReady,
downloadCombatMediaPack,
removeCombatMediaPack,
} from "../game/combatMediaPack";
export interface OptionsPanelSettings {
showMobHealthBars: boolean;
@@ -68,6 +73,12 @@ const TOGGLES: readonly ToggleOption[] = [
const COMBAT_ACTOR_GROUPS: readonly CombatActorGroup[] = ["player", "party", "enemy"];
const VFX_QUALITY_ORDER: readonly CombatVfxQuality[] = ["off", "low", "high"];
type CombatMediaActionState =
| { readonly kind: "idle" }
| { readonly kind: "downloading"; readonly completed: number; readonly total: number }
| { readonly kind: "removing" }
| { readonly kind: "error"; readonly message: string };
export function nextCombatVfxMode(mode: CombatVfxMode): CombatVfxMode {
return mode === "generated" ? "original" : "generated";
}
@@ -90,6 +101,9 @@ function OptionsDialog({
onPresentationReset,
onClose,
}: Required<Omit<OptionsPanelProps, "open">>) {
const [combatMediaAction, setCombatMediaAction] = useState<CombatMediaActionState>({ kind: "idle" });
const combatMediaInstalled = combatMediaPackReady();
const originalMediaReady = presentationSettings.originalMediaEnabled && combatMediaInstalled;
const patchGroupBoolean = (
field: "vfxEnabled" | "audioEnabled",
group: CombatActorGroup,
@@ -102,9 +116,47 @@ function OptionsDialog({
const index = VFX_QUALITY_ORDER.indexOf(presentationSettings.vfxQuality);
onPresentationChange({ vfxQuality: VFX_QUALITY_ORDER[(index + 1) % VFX_QUALITY_ORDER.length] });
};
const cycleVfxMode = () => onPresentationChange({
vfxMode: nextCombatVfxMode(presentationSettings.vfxMode),
});
const cycleVfxMode = () => {
if (!originalMediaReady) return;
onPresentationChange({ vfxMode: nextCombatVfxMode(presentationSettings.vfxMode) });
};
const toggleOriginalMedia = () => {
if (combatMediaAction.kind === "downloading" || combatMediaAction.kind === "removing") return;
if (combatMediaInstalled && !presentationSettings.originalMediaEnabled) {
onPresentationChange({ originalMediaEnabled: true });
return;
}
if (combatMediaInstalled) {
setCombatMediaAction({ kind: "removing" });
void removeCombatMediaPack().then(() => {
onPresentationChange({ originalMediaEnabled: false, vfxMode: "generated" });
setCombatMediaAction({ kind: "idle" });
}).catch((error: unknown) => setCombatMediaAction({
kind: "error",
message: error instanceof Error ? error.message : "Could not remove the local combat media.",
}));
return;
}
setCombatMediaAction({ kind: "downloading", completed: 0, total: 1 });
void downloadCombatMediaPack(({ completed, total }) => {
setCombatMediaAction({ kind: "downloading", completed, total });
}).then(() => {
onPresentationChange({ originalMediaEnabled: true });
setCombatMediaAction({ kind: "idle" });
}).catch((error: unknown) => setCombatMediaAction({
kind: "error",
message: error instanceof Error ? error.message : "Could not download the combat media.",
}));
};
const combatMediaButtonLabel = combatMediaAction.kind === "downloading"
? `Downloading ${Math.round(combatMediaAction.completed / Math.max(1, combatMediaAction.total) * 100)}%`
: combatMediaAction.kind === "removing"
? "Removing…"
: combatMediaInstalled && !presentationSettings.originalMediaEnabled
? "Enable"
: combatMediaInstalled
? "Remove"
: "Download";
const cycleGameplayFrameRate = () => onChange({
gameplayFrameRate: nextFrameRateLimit(settings.gameplayFrameRate, GAMEPLAY_FRAME_RATE_LIMITS),
});
@@ -124,6 +176,7 @@ function OptionsDialog({
{ id: "option-scale-up", run: () => onChange({ uiScale: quantize(settings.uiScale + 0.05, 0.05, 0.75, 1.25) }) },
{ id: "option-minimap", run: () => onChange({ minimapRotation: settings.minimapRotation === "north-up" ? "player-up" : "north-up" }) },
{ id: "option-threat-position", run: () => onChange({ threatMeterPosition: DEFAULT_THREAT_METER_POSITION }) },
{ id: "option-original-media", run: toggleOriginalMedia },
{ id: "option-vfx-mode", run: cycleVfxMode },
{ id: "option-vfx-quality", run: cycleVfxQuality },
...COMBAT_ACTOR_GROUPS.flatMap((group) => [
@@ -145,7 +198,7 @@ function OptionsDialog({
},
]),
{ id: "option-reset", run: () => { onReset(); onPresentationReset(); } },
], [onChange, onPresentationChange, onPresentationReset, onReset, presentationSettings, settings]);
], [combatMediaAction.kind, onChange, onPresentationChange, onPresentationReset, onReset, presentationSettings, settings]);
const controller = useMenuController(actions, { columns: 1, onBack: onClose });
const optionButton = (id: string, label: string, run: () => void, disabled = false) => (
@@ -229,10 +282,31 @@ function OptionsDialog({
}))}
</div>
<h3 className="options-panel__section-title">Combat presentation</h3>
<div className="option-row">
<span>
<strong>Original spell effects &amp; sounds</strong>
<small>{originalMediaReady
? "Stored on this device. Original visuals and authentic combat sounds are available."
: combatMediaInstalled
? "Stored on this device. Enable the pack to use original visuals and authentic combat sounds."
: "Not downloaded by default. Download this optional media pack to unlock original visuals and authentic combat sounds."}</small>
{combatMediaAction.kind === "error"
? <small role="status">{combatMediaAction.message}</small>
: null}
</span>
{optionButton(
"option-original-media",
combatMediaButtonLabel,
toggleOriginalMedia,
combatMediaAction.kind === "downloading" || combatMediaAction.kind === "removing",
)}
</div>
<div className="option-row">
<span>
<strong>Spell visual mode</strong>
<small>{presentationSettings.vfxMode === "original"
<small>{!originalMediaReady
? "Generated effects are used until the optional original media pack is downloaded."
: presentationSettings.vfxMode === "original"
? "Use only packaged original source effects. Unsupported spells show no visual effect."
: "Use the current generated combat effects."}</small>
</span>
@@ -240,6 +314,7 @@ function OptionsDialog({
"option-vfx-mode",
presentationSettings.vfxMode === "original" ? "Original" : "Generated",
cycleVfxMode,
!originalMediaReady,
)}
</div>
<div className="option-row">