feat: another balance and bug fix pass (#238)
Security Scan and Upload / Security & DefectDojo Upload (push) Successful in 1m10s
CI / Lint, Build & Test (push) Successful in 1m13s

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:
2026-04-06 18:17:00 -07:00
committed by Naomi Carrigan
parent b0227c1709
commit 1195b657a0
34 changed files with 980 additions and 203 deletions
+65 -24
View File
@@ -27,12 +27,12 @@ const baseThreshold = 1_000_000;
/**
* Calculates the prestige threshold for a given prestige count.
* Mirrors the server formula: BASE * (count + 1)^2.
* Mirrors the server formula: BASE * (count + 1)^2.5.
* @param prestigeCount - The current prestige count.
* @returns The required gold to prestige.
*/
const calculateThreshold = (prestigeCount: number): number => {
return baseThreshold * Math.pow(prestigeCount + 1, 2);
return baseThreshold * Math.pow(prestigeCount + 1, 2.5);
};
/**
@@ -41,7 +41,7 @@ const calculateThreshold = (prestigeCount: number): number => {
* @returns The compounding multiplier applied to all income sources.
*/
const calculateProductionMultiplier = (prestigeCount: number): number => {
return Math.pow(1.15, prestigeCount);
return Math.pow(1.3, prestigeCount);
};
const categoryOrder: Array<PrestigeUpgradeCategory> = [
@@ -61,12 +61,14 @@ const PrestigePanel = (): JSX.Element => {
const {
state,
reloadSilent,
formatInteger,
formatNumber,
buyPrestigeUpgrade,
enableNotifications,
enableSounds,
toggleAutoAdventurer,
toggleAutoPrestige,
toggleAutoPrestigeMaxRunestones,
triggerPrestigeToast,
} = useGame();
const [ isPending, setIsPending ] = useState(false);
@@ -92,6 +94,11 @@ const PrestigePanel = (): JSX.Element => {
const isEligible = player.totalGoldEarned >= threshold;
const runestonePreview = computeProjectedRunestones(state);
const nextMultiplier = calculateProductionMultiplier(prestigeData.count + 1);
const baseRunestones = Math.min(
Math.floor(Math.cbrt(player.totalGoldEarned / threshold)) * 15,
200,
);
const isAtMaxRunestones = baseRunestones >= 200;
async function handlePrestige(): Promise<void> {
setIsPending(true);
@@ -154,6 +161,10 @@ const PrestigePanel = (): JSX.Element => {
toggleAutoPrestige();
}
function handleAutoPrestigeMaxRunestonesToggle(): void {
toggleAutoPrestigeMaxRunestones();
}
function handlePrestigeTabClick(): void {
setActiveTab("prestige");
}
@@ -187,7 +198,7 @@ const PrestigePanel = (): JSX.Element => {
type="button"
>
{"🔮 Runestone Shop ("}
{formatNumber(prestigeData.runestones)}
{formatInteger(prestigeData.runestones)}
{" stones)"}
</button>
</div>
@@ -198,7 +209,7 @@ const PrestigePanel = (): JSX.Element => {
{"Prestige resets your progress but grants "}
<strong>{"Runestones"}</strong>
{"— permanent currency used for powerful upgrades."}
{" Each prestige multiplies your global production by ×1.15"}
{" Each prestige multiplies your global production by ×1.3"}
{" (compounding each run)."}
</p>
@@ -231,15 +242,25 @@ const PrestigePanel = (): JSX.Element => {
</p>
<p>
{"Runestones: "}
<strong>{formatNumber(prestigeData.runestones)}</strong>
<strong>{formatInteger(prestigeData.runestones)}</strong>
</p>
{isEligible
? <p className="runestone-preview">
{"Runestones on prestige: "}
<strong>
{"+"}
{formatNumber(runestonePreview)}
{formatInteger(runestonePreview)}
</strong>
{isAtMaxRunestones
? <span className="runestone-max-badge">{" ⚡ MAX"}</span>
: null
}
</p>
: null}
{isEligible && !isAtMaxRunestones
? <p className="runestone-progress-hint">
{"Earn more gold to increase your runestone yield "
+ "(capped at ×14³ the prestige threshold)."}
</p>
: null}
{isEligible
@@ -268,7 +289,7 @@ const PrestigePanel = (): JSX.Element => {
>
{isPending
? "Ascending..."
: `✨ Ascend (+${formatNumber(runestonePreview)} Runestones)`}
: `✨ Ascend (+${formatInteger(runestonePreview)} Runestones)`}
</button>
{prestigeError === null
? null
@@ -280,12 +301,12 @@ const PrestigePanel = (): JSX.Element => {
{"Ascended to Prestige "}
{result.count}
{"! Earned "}
{formatNumber(result.runestones)}
{formatInteger(result.runestones)}
{" Runestones."}
{result.milestoneRunestones > 0
&& <>
{" 🎉 Milestone bonus: +"}
{formatNumber(result.milestoneRunestones)}
{formatInteger(result.milestoneRunestones)}
{" Runestones!"}
</>
}
@@ -306,7 +327,7 @@ const PrestigePanel = (): JSX.Element => {
<p className="shop-balance">
{"Balance: "}
<strong>
{formatNumber(prestigeData.runestones)}
{formatInteger(prestigeData.runestones)}
{" Runestones"}
</strong>
</p>
@@ -331,6 +352,8 @@ const PrestigePanel = (): JSX.Element => {
= upgrade.id === "auto_prestige" && purchased;
const autoPrestigeEnabled
= prestigeData.autoPrestigeEnabled ?? false;
const autoPrestigeMaxRunestonesOnly
= prestigeData.autoPrestigeMaxRunestonesOnly ?? false;
function handleBuyClick(): void {
void handleBuyUpgrade(upgrade.id);
@@ -358,7 +381,7 @@ const PrestigePanel = (): JSX.Element => {
<p className="upgrade-cost">
{purchased
? "✅ Purchased"
: `🔮 ${formatNumber(upgrade.runestonesCost)} Runestones`}
: `🔮 ${formatInteger(upgrade.runestonesCost)} Runestones`}
</p>
</div>
{isAutoAdventurerToggle
@@ -377,19 +400,37 @@ const PrestigePanel = (): JSX.Element => {
</button>
: null}
{isAutoPrestigeToggle
? <button
className={`auto-prestige-toggle ${
autoPrestigeEnabled
? "enabled"
: "disabled"
}`}
onClick={handleAutoPrestigeToggle}
type="button"
>
? <>
<button
className={`auto-prestige-toggle ${
autoPrestigeEnabled
? "enabled"
: "disabled"
}`}
onClick={handleAutoPrestigeToggle}
type="button"
>
{autoPrestigeEnabled
? "⚡ Auto ON"
: "⏸ Auto OFF"}
</button>
{autoPrestigeEnabled
? "⚡ Auto ON"
: "⏸ Auto OFF"}
</button>
? <button
className={`auto-prestige-toggle ${
autoPrestigeMaxRunestonesOnly
? "enabled"
: "disabled"
}`}
onClick={handleAutoPrestigeMaxRunestonesToggle}
title="Only fire at max runestone yield"
type="button"
>
{autoPrestigeMaxRunestonesOnly
? "⚡ Max Runes Only"
: "⏸ Max Runes OFF"}
</button>
: null}
</>
: null}
{purchased
? null