generated from nhcarrigan/template
feat: another balance and bug fix pass (#238)
Working through open issues — fixes, balance changes, and features. ## Closed - Closes #161 - Closes #181 - Closes #191 - Closes #199 - Closes #201 - Closes #202 - Closes #203 - Closes #204 - Closes #205 - Closes #206 - Closes #208 - Closes #211 - Closes #212 - Closes #213 - Closes #214 - Closes #216 - Closes #219 - Closes #220 - Closes #221 - Closes #222 - Closes #224 - Closes #225 - Closes #226 - Closes #228 - Closes #229 - Closes #230 - Closes #231 - Closes #232 - Closes #233 - Closes #234 - Closes #235 - Closes #236 ✨ This PR was created with help from Hikari~ 🌸 Reviewed-on: #238 Co-authored-by: Hikari <hikari@nhcarrigan.com> Co-committed-by: Hikari <hikari@nhcarrigan.com>
This commit was merged in pull request #238.
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
type NumberFormat,
|
||||
type Quest,
|
||||
type TranscendenceResponse,
|
||||
computeUnlockedCompanionIds,
|
||||
isStoryChapterUnlocked,
|
||||
} from "@elysium/types";
|
||||
import {
|
||||
@@ -62,14 +63,16 @@ import {
|
||||
computePartyCombatPower,
|
||||
} from "../engine/tick.js";
|
||||
import { updateChallengeProgress } from "../utils/dailyChallenges.js";
|
||||
import { formatNumber as formatNumberUtil } from "../utils/format.js";
|
||||
import {
|
||||
formatInteger as formatIntegerUtil,
|
||||
formatNumber as formatNumberUtil,
|
||||
} from "../utils/format.js";
|
||||
import { logError } from "../utils/logError.js";
|
||||
import { sendNotification } from "../utils/notification.js";
|
||||
import { playSound } from "../utils/sound.js";
|
||||
|
||||
const autoSaveIntervalMs = 30_000;
|
||||
const autoPrestigeThresholdBase = 1_000_000;
|
||||
const autoPrestigeThresholdScale = 5;
|
||||
|
||||
/**
|
||||
* Pure function — applies a boss challenge result to the game state.
|
||||
@@ -461,6 +464,11 @@ interface GameContextValue {
|
||||
*/
|
||||
formatNumber: (value: number)=> string;
|
||||
|
||||
/**
|
||||
* Format a whole-number value without decimal places.
|
||||
*/
|
||||
formatInteger: (value: number)=> string;
|
||||
|
||||
/**
|
||||
* Buy a prestige upgrade from the runestone shop.
|
||||
*/
|
||||
@@ -471,6 +479,11 @@ interface GameContextValue {
|
||||
*/
|
||||
toggleAutoPrestige: ()=> void;
|
||||
|
||||
/**
|
||||
* Toggle whether auto-prestige waits for maximum runestone yield before firing.
|
||||
*/
|
||||
toggleAutoPrestigeMaxRunestones: ()=> void;
|
||||
|
||||
/**
|
||||
* Toggle the auto-quest setting on/off.
|
||||
*/
|
||||
@@ -1111,6 +1124,57 @@ export const GameProvider = ({
|
||||
});
|
||||
}, [ state ]);
|
||||
|
||||
// Detect newly unlocked companions whenever relevant state changes
|
||||
useEffect(() => {
|
||||
if (state === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const computedUnlocks = computeUnlockedCompanionIds({
|
||||
apotheosisCount: state.apotheosis?.count ?? 0,
|
||||
lifetimeBossesDefeated: state.player.lifetimeBossesDefeated,
|
||||
lifetimeGoldEarned: state.player.lifetimeGoldEarned,
|
||||
lifetimeQuestsCompleted: state.player.lifetimeQuestsCompleted,
|
||||
prestigeCount: state.prestige.count,
|
||||
transcendenceCount: state.transcendence?.count ?? 0,
|
||||
});
|
||||
|
||||
const currentUnlocks = state.companions?.unlockedCompanionIds ?? [];
|
||||
const toAdd = computedUnlocks.filter((id) => {
|
||||
return !currentUnlocks.includes(id);
|
||||
});
|
||||
|
||||
if (toAdd.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState((previous) => {
|
||||
if (previous === null) {
|
||||
return previous;
|
||||
}
|
||||
const existingUnlocks = previous.companions?.unlockedCompanionIds ?? [];
|
||||
const addedIds = computedUnlocks.filter((id) => {
|
||||
return !existingUnlocks.includes(id);
|
||||
});
|
||||
if (addedIds.length === 0) {
|
||||
return previous;
|
||||
}
|
||||
const updatedUnlocks = [ ...existingUnlocks, ...addedIds ];
|
||||
const activeId = previous.companions?.activeCompanionId ?? null;
|
||||
const validatedActiveId
|
||||
= activeId !== null && updatedUnlocks.includes(activeId)
|
||||
? activeId
|
||||
: null;
|
||||
return {
|
||||
...previous,
|
||||
companions: {
|
||||
activeCompanionId: validatedActiveId,
|
||||
unlockedCompanionIds: updatedUnlocks,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, [ state ]);
|
||||
|
||||
// Game loop via requestAnimationFrame
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1332,14 +1396,27 @@ export const GameProvider = ({
|
||||
|
||||
// Auto-prestige: fire when unlocked, enabled, and threshold is met
|
||||
const autoState = stateReference.current;
|
||||
const autoPrestigeThreshold = autoPrestigeThresholdBase
|
||||
* Math.pow((autoState?.prestige.count ?? 0) + 1, 2.5)
|
||||
* (autoState?.transcendence?.echoPrestigeThresholdMultiplier ?? 1);
|
||||
const autoBaseRunestones = Math.min(
|
||||
Math.floor(
|
||||
Math.cbrt(
|
||||
(autoState?.player.totalGoldEarned ?? 0) / autoPrestigeThreshold,
|
||||
),
|
||||
) * 15,
|
||||
200,
|
||||
);
|
||||
const autoMaxRunestonesMet
|
||||
= autoState?.prestige.autoPrestigeMaxRunestonesOnly !== true
|
||||
|| autoBaseRunestones >= 200;
|
||||
if (
|
||||
!isAutoPrestigingReference.current
|
||||
&& autoState?.prestige.purchasedUpgradeIds.includes("auto_prestige")
|
||||
=== true
|
||||
&& autoState.prestige.autoPrestigeEnabled === true
|
||||
&& autoState.player.totalGoldEarned
|
||||
>= autoPrestigeThresholdBase
|
||||
* Math.pow(autoPrestigeThresholdScale, autoState.prestige.count)
|
||||
&& autoState.player.totalGoldEarned >= autoPrestigeThreshold
|
||||
&& autoMaxRunestonesMet
|
||||
) {
|
||||
isAutoPrestigingReference.current = true;
|
||||
void prestigeApi({}).
|
||||
@@ -2005,6 +2082,22 @@ export const GameProvider = ({
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleAutoPrestigeMaxRunestones = useCallback(() => {
|
||||
setState((previous) => {
|
||||
if (previous === null) {
|
||||
return previous;
|
||||
}
|
||||
return {
|
||||
...previous,
|
||||
prestige: {
|
||||
...previous.prestige,
|
||||
autoPrestigeMaxRunestonesOnly:
|
||||
previous.prestige.autoPrestigeMaxRunestonesOnly !== true,
|
||||
},
|
||||
};
|
||||
});
|
||||
}, []);
|
||||
|
||||
const toggleAutoQuest = useCallback(() => {
|
||||
setState((previous) => {
|
||||
if (previous === null) {
|
||||
@@ -2359,6 +2452,13 @@ export const GameProvider = ({
|
||||
[ numberFormat ],
|
||||
);
|
||||
|
||||
const formatInteger = useCallback(
|
||||
(value: number) => {
|
||||
return formatIntegerUtil(value);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const contextValue = useMemo<GameContextValue>(() => {
|
||||
return {
|
||||
apotheosis,
|
||||
@@ -2397,6 +2497,7 @@ export const GameProvider = ({
|
||||
flushBossLoreToasts,
|
||||
forceSync,
|
||||
forceUnlocks,
|
||||
formatInteger,
|
||||
formatNumber,
|
||||
handleClick,
|
||||
inGuild,
|
||||
@@ -2428,6 +2529,7 @@ export const GameProvider = ({
|
||||
toggleAutoAdventurer,
|
||||
toggleAutoBoss,
|
||||
toggleAutoPrestige,
|
||||
toggleAutoPrestigeMaxRunestones,
|
||||
toggleAutoQuest,
|
||||
transcend,
|
||||
triggerPrestigeToast,
|
||||
@@ -2443,6 +2545,7 @@ export const GameProvider = ({
|
||||
bossError,
|
||||
completedQuestToasts,
|
||||
failedQuestToasts,
|
||||
formatInteger,
|
||||
formatNumber,
|
||||
buyAdventurer,
|
||||
buyEchoUpgrade,
|
||||
@@ -2502,6 +2605,7 @@ export const GameProvider = ({
|
||||
toggleAutoAdventurer,
|
||||
toggleAutoBoss,
|
||||
toggleAutoPrestige,
|
||||
toggleAutoPrestigeMaxRunestones,
|
||||
toggleAutoQuest,
|
||||
transcend,
|
||||
triggerPrestigeToast,
|
||||
|
||||
Reference in New Issue
Block a user