generated from nhcarrigan/template
fix: turn off auto-boss/auto-quest on failure and surface status
Auto-boss now disables itself when a boss fight is lost (instead of silently looping), and turns off with an error message when the API call itself fails (e.g. party has no adventurers). Auto-quest disables itself whenever a quest fails the random chance check. In both cases the player sees a clear status/error line in the boss panel rather than a silent toggle, and can re-enable when ready. Also initialises autoBoss/autoQuest to false in initialGameState so these fields survive save/load cycles from the very first session. Closes #40
This commit is contained in:
@@ -76,6 +76,8 @@ const initialGameState = (
|
||||
achievements: structuredClone(defaultAchievements),
|
||||
adventurers: structuredClone(defaultAdventurers),
|
||||
apotheosis: { ...initialApotheosis },
|
||||
autoBoss: false,
|
||||
autoQuest: false,
|
||||
baseClickPower: 1,
|
||||
bosses: structuredClone(defaultBosses),
|
||||
companions: { activeCompanionId: null, unlockedCompanionIds: [] },
|
||||
|
||||
@@ -226,7 +226,14 @@ const computePartyStats = (
|
||||
* @returns The JSX element.
|
||||
*/
|
||||
const BossPanel = (): JSX.Element => {
|
||||
const { state, challengeBoss, formatNumber, toggleAutoBoss } = useGame();
|
||||
const {
|
||||
state,
|
||||
challengeBoss,
|
||||
formatNumber,
|
||||
toggleAutoBoss,
|
||||
autoBossLastResult,
|
||||
autoBossError,
|
||||
} = useGame();
|
||||
const [ challengingBossId, setChallengingBossId ] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
@@ -346,6 +353,23 @@ const BossPanel = (): JSX.Element => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{autoBossError === null
|
||||
? null
|
||||
: <p className="auto-boss-error">
|
||||
{"⚠️ Auto-boss stopped: "}
|
||||
{autoBossError}
|
||||
</p>
|
||||
}
|
||||
{autoBossLastResult !== null && autoBossError === null
|
||||
? <p className="auto-boss-status">
|
||||
{"🤖 Last fight: "}
|
||||
{autoBossLastResult.bossName}
|
||||
{autoBossLastResult.won
|
||||
? " — ✅ Won"
|
||||
: " — ❌ Lost"}
|
||||
</p>
|
||||
: null}
|
||||
|
||||
<ZoneSelector
|
||||
activeZoneId={activeZoneId}
|
||||
onSelectZone={setActiveZoneId}
|
||||
|
||||
@@ -545,6 +545,18 @@ interface GameContextValue {
|
||||
* Reset all progress to a fresh save state (resolves schema outdated).
|
||||
*/
|
||||
resetProgress: ()=> Promise<void>;
|
||||
|
||||
/**
|
||||
* Last auto-boss fight result — null until the first auto fight completes or
|
||||
* when auto-boss is toggled off.
|
||||
*/
|
||||
autoBossLastResult: { bossName: string; won: boolean; at: number } | null;
|
||||
|
||||
/**
|
||||
* Error message set when auto-boss stopped due to a critical failure (null
|
||||
* when no error). Cleared automatically when the player re-enables auto-boss.
|
||||
*/
|
||||
autoBossError: string | null;
|
||||
}
|
||||
|
||||
export interface BattleResult {
|
||||
@@ -588,6 +600,12 @@ export const GameProvider = ({
|
||||
const [ lastSavedAt, setLastSavedAt ] = useState<number | null>(null);
|
||||
const [ isSyncing, setIsSyncing ] = useState(false);
|
||||
const [ syncError, setSyncError ] = useState<string | null>(null);
|
||||
const [ autoBossLastResult, setAutoBossLastResult ] = useState<{
|
||||
bossName: string;
|
||||
won: boolean;
|
||||
at: number;
|
||||
} | null>(null);
|
||||
const [ autoBossError, setAutoBossError ] = useState<string | null>(null);
|
||||
const syncErrorTimerReference = useRef<ReturnType<typeof setTimeout> | null>(
|
||||
null,
|
||||
);
|
||||
@@ -1059,6 +1077,14 @@ export const GameProvider = ({
|
||||
},
|
||||
);
|
||||
|
||||
// Quest failure — turn off auto-quest so the player can reassess
|
||||
if (
|
||||
newlyFailedQuestsReference.current.length > 0
|
||||
&& next.autoQuest === true
|
||||
) {
|
||||
next = { ...next, autoQuest: false };
|
||||
}
|
||||
|
||||
return next;
|
||||
});
|
||||
|
||||
@@ -1200,14 +1226,32 @@ export const GameProvider = ({
|
||||
if (previous === null) {
|
||||
return previous;
|
||||
}
|
||||
return applyBossResult(previous, bossId, result);
|
||||
const afterBoss = applyBossResult(previous, bossId, result);
|
||||
// Defeat — turn off auto-boss so the player can reassess
|
||||
if (!result.won) {
|
||||
return { ...afterBoss, autoBoss: false };
|
||||
}
|
||||
return afterBoss;
|
||||
});
|
||||
setAutoBossLastResult({
|
||||
at: Date.now(),
|
||||
bossName: bossName,
|
||||
won: result.won,
|
||||
});
|
||||
setBattleResult({ bossName, result });
|
||||
}).
|
||||
catch((error_: unknown) => {
|
||||
logError("auto_boss", error_);
|
||||
|
||||
/* Silently ignore — will retry next tick */
|
||||
const message
|
||||
= error_ instanceof Error
|
||||
? error_.message
|
||||
: String(error_);
|
||||
setAutoBossError(message);
|
||||
setState((previous) => {
|
||||
if (previous === null) {
|
||||
return previous;
|
||||
}
|
||||
return { ...previous, autoBoss: false };
|
||||
});
|
||||
}).
|
||||
finally(() => {
|
||||
isAutoBossingReference.current = false;
|
||||
@@ -1782,6 +1826,8 @@ export const GameProvider = ({
|
||||
}, []);
|
||||
|
||||
const toggleAutoBoss = useCallback(() => {
|
||||
setAutoBossError(null);
|
||||
setAutoBossLastResult(null);
|
||||
setState((previous) => {
|
||||
if (previous === null) {
|
||||
return previous;
|
||||
@@ -1974,6 +2020,8 @@ export const GameProvider = ({
|
||||
const contextValue = useMemo<GameContextValue>(() => {
|
||||
return {
|
||||
apotheosis,
|
||||
autoBossError,
|
||||
autoBossLastResult,
|
||||
battleResult,
|
||||
buyAdventurer,
|
||||
buyEchoUpgrade,
|
||||
@@ -2040,6 +2088,8 @@ export const GameProvider = ({
|
||||
};
|
||||
}, [
|
||||
apotheosis,
|
||||
autoBossError,
|
||||
autoBossLastResult,
|
||||
battleResult,
|
||||
completedQuestToasts,
|
||||
failedQuestToasts,
|
||||
|
||||
Reference in New Issue
Block a user