generated from nhcarrigan/template
feat: add milestone prestige bonuses every 5th prestige
Every 5th prestige awards a scaling runestone windfall: milestone_number * 25 stones (prestige 5 = 25, 10 = 50, 50 = 250, etc). Shown in the ascension success message when non-zero.
This commit is contained in:
@@ -12,7 +12,7 @@ A running list of planned features and content additions. Strike through items a
|
|||||||
|
|
||||||
- [x] **Daily challenges** — Three rotating objectives each day (e.g. kill X boss, earn X gold this run, complete X quests). Reward bonus crystals. Encourages daily logins even when idling comfortably.
|
- [x] **Daily challenges** — Three rotating objectives each day (e.g. kill X boss, earn X gold this run, complete X quests). Reward bonus crystals. Encourages daily logins even when idling comfortably.
|
||||||
|
|
||||||
- [ ] **Boss first-kill bounties** — Defeating a boss for the very first time grants a one-time runestone bonus. Rewards exploration and makes conquering a new zone feel extra satisfying.
|
- [x] **Boss first-kill bounties** — Defeating a boss for the very first time grants a one-time runestone bonus. Rewards exploration and makes conquering a new zone feel extra satisfying.
|
||||||
|
|
||||||
- [ ] **Auto-prestige toggle** — Unlockable via the prestige shop. Automatically prestiges the moment the threshold is reached. Late-game convenience that dedicated players will love.
|
- [ ] **Auto-prestige toggle** — Unlockable via the prestige shop. Automatically prestiges the moment the threshold is reached. Late-game convenience that dedicated players will love.
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ A running list of planned features and content additions. Strike through items a
|
|||||||
|
|
||||||
- [ ] **The Codex / Lore Book** — Defeating bosses and completing quests unlocks lore entries about the world. Pure flavour, but gives the world depth and a collection mechanic. Show a ✨ notification when new lore unlocks.
|
- [ ] **The Codex / Lore Book** — Defeating bosses and completing quests unlocks lore entries about the world. Pure flavour, but gives the world depth and a collection mechanic. Show a ✨ notification when new lore unlocks.
|
||||||
|
|
||||||
- [ ] **Milestone prestige bonuses** — Every 5th prestige, earn a free prestige upgrade or a large runestone windfall. Gives players mini-goals within the prestige loop.
|
- [x] **Milestone prestige bonuses** — Every 5th prestige, earn a free prestige upgrade or a large runestone windfall. Gives players mini-goals within the prestige loop.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,8 +41,8 @@ A running list of planned features and content additions. Strike through items a
|
|||||||
1. ~~Offline earnings~~ ✅
|
1. ~~Offline earnings~~ ✅
|
||||||
2. ~~Statistics panel~~ ✅
|
2. ~~Statistics panel~~ ✅
|
||||||
3. ~~Daily challenges~~ ✅
|
3. ~~Daily challenges~~ ✅
|
||||||
4. Boss first-kill bounties (easy content win)
|
4. ~~Boss first-kill bounties~~ ✅
|
||||||
5. Milestone prestige bonuses (easy content win)
|
5. ~~Milestone prestige bonuses~~ ✅
|
||||||
6. Equipment set bonuses (medium effort)
|
6. Equipment set bonuses (medium effort)
|
||||||
7. Auto-prestige toggle (prestige shop upgrade)
|
7. Auto-prestige toggle (prestige shop upgrade)
|
||||||
8. The Codex / Lore Book (flavour, lower priority)
|
8. The Codex / Lore Book (flavour, lower priority)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ prestigeRouter.post("/", async (context) => {
|
|||||||
challengeCrystals = result.crystalsAwarded;
|
challengeCrystals = result.crystalsAwarded;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { newState, newPrestigeData, runestonesEarned } = buildPostPrestigeState(
|
const { newState, newPrestigeData, runestonesEarned, milestoneRunestones } = buildPostPrestigeState(
|
||||||
state,
|
state,
|
||||||
characterName,
|
characterName,
|
||||||
);
|
);
|
||||||
@@ -81,6 +81,7 @@ prestigeRouter.post("/", async (context) => {
|
|||||||
return context.json({
|
return context.json({
|
||||||
runestones: runestonesEarned,
|
runestones: runestonesEarned,
|
||||||
newPrestigeCount: newPrestigeData.count,
|
newPrestigeCount: newPrestigeData.count,
|
||||||
|
milestoneRunestones,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { DEFAULT_PRESTIGE_UPGRADES } from "../data/prestigeUpgrades.js";
|
|||||||
const BASE_PRESTIGE_GOLD_THRESHOLD = 1_000_000;
|
const BASE_PRESTIGE_GOLD_THRESHOLD = 1_000_000;
|
||||||
const THRESHOLD_SCALE_FACTOR = 5;
|
const THRESHOLD_SCALE_FACTOR = 5;
|
||||||
const RUNESTONES_PER_PRESTIGE_LEVEL = 10;
|
const RUNESTONES_PER_PRESTIGE_LEVEL = 10;
|
||||||
|
const MILESTONE_INTERVAL = 5;
|
||||||
|
const MILESTONE_RUNESTONES_PER_INTERVAL = 25;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the gold threshold required for the next prestige.
|
* Calculates the gold threshold required for the next prestige.
|
||||||
@@ -65,6 +67,16 @@ export const calculateRunestones = (
|
|||||||
export const calculateProductionMultiplier = (prestigeCount: number): number =>
|
export const calculateProductionMultiplier = (prestigeCount: number): number =>
|
||||||
Math.pow(1.15, prestigeCount);
|
Math.pow(1.15, prestigeCount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the milestone runestone bonus for the given prestige count.
|
||||||
|
* Every MILESTONE_INTERVAL prestiges awards milestone_number * MILESTONE_RUNESTONES_PER_INTERVAL stones.
|
||||||
|
*/
|
||||||
|
export const calculateMilestoneBonus = (newPrestigeCount: number): number => {
|
||||||
|
if (newPrestigeCount % MILESTONE_INTERVAL !== 0) return 0;
|
||||||
|
const milestoneNumber = newPrestigeCount / MILESTONE_INTERVAL;
|
||||||
|
return milestoneNumber * MILESTONE_RUNESTONES_PER_INTERVAL;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the reset game state after a prestige.
|
* Generates the reset game state after a prestige.
|
||||||
* Carries over prestige data and runestones; resets everything else.
|
* Carries over prestige data and runestones; resets everything else.
|
||||||
@@ -72,7 +84,7 @@ export const calculateProductionMultiplier = (prestigeCount: number): number =>
|
|||||||
export const buildPostPrestigeState = (
|
export const buildPostPrestigeState = (
|
||||||
currentState: GameState,
|
currentState: GameState,
|
||||||
characterName: string,
|
characterName: string,
|
||||||
): { newState: GameState; newPrestigeData: PrestigeData; runestonesEarned: number } => {
|
): { newState: GameState; newPrestigeData: PrestigeData; runestonesEarned: number; milestoneRunestones: number } => {
|
||||||
const runestonesEarned = calculateRunestones(
|
const runestonesEarned = calculateRunestones(
|
||||||
currentState.player.totalGoldEarned,
|
currentState.player.totalGoldEarned,
|
||||||
currentState.prestige.count,
|
currentState.prestige.count,
|
||||||
@@ -80,10 +92,11 @@ export const buildPostPrestigeState = (
|
|||||||
);
|
);
|
||||||
const newPrestigeCount = currentState.prestige.count + 1;
|
const newPrestigeCount = currentState.prestige.count + 1;
|
||||||
const { purchasedUpgradeIds } = currentState.prestige;
|
const { purchasedUpgradeIds } = currentState.prestige;
|
||||||
|
const milestoneRunestones = calculateMilestoneBonus(newPrestigeCount);
|
||||||
|
|
||||||
const newPrestigeData: PrestigeData = {
|
const newPrestigeData: PrestigeData = {
|
||||||
count: newPrestigeCount,
|
count: newPrestigeCount,
|
||||||
runestones: currentState.prestige.runestones + runestonesEarned,
|
runestones: currentState.prestige.runestones + runestonesEarned + milestoneRunestones,
|
||||||
productionMultiplier: calculateProductionMultiplier(newPrestigeCount),
|
productionMultiplier: calculateProductionMultiplier(newPrestigeCount),
|
||||||
purchasedUpgradeIds,
|
purchasedUpgradeIds,
|
||||||
lastPrestigedAt: Date.now(),
|
lastPrestigedAt: Date.now(),
|
||||||
@@ -97,5 +110,5 @@ export const buildPostPrestigeState = (
|
|||||||
lastTickAt: Date.now(),
|
lastTickAt: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return { newState, newPrestigeData, runestonesEarned };
|
return { newState, newPrestigeData, runestonesEarned, milestoneRunestones };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const PrestigePanel = (): React.JSX.Element => {
|
|||||||
const { state, reload, formatNumber, buyPrestigeUpgrade } = useGame();
|
const { state, reload, formatNumber, buyPrestigeUpgrade } = useGame();
|
||||||
const [characterName, setCharacterName] = useState("");
|
const [characterName, setCharacterName] = useState("");
|
||||||
const [isPending, setIsPending] = useState(false);
|
const [isPending, setIsPending] = useState(false);
|
||||||
const [result, setResult] = useState<{ runestones: number; count: number } | null>(null);
|
const [result, setResult] = useState<{ runestones: number; count: number; milestoneRunestones: number } | null>(null);
|
||||||
const [prestigeError, setPrestigeError] = useState<string | null>(null);
|
const [prestigeError, setPrestigeError] = useState<string | null>(null);
|
||||||
const [buyingId, setBuyingId] = useState<string | null>(null);
|
const [buyingId, setBuyingId] = useState<string | null>(null);
|
||||||
const [activeTab, setActiveTab] = useState<"prestige" | "shop">("prestige");
|
const [activeTab, setActiveTab] = useState<"prestige" | "shop">("prestige");
|
||||||
@@ -65,7 +65,7 @@ export const PrestigePanel = (): React.JSX.Element => {
|
|||||||
setPrestigeError(null);
|
setPrestigeError(null);
|
||||||
try {
|
try {
|
||||||
const data = await prestige({ characterName: characterName.trim() });
|
const data = await prestige({ characterName: characterName.trim() });
|
||||||
setResult({ runestones: data.runestones, count: data.newPrestigeCount });
|
setResult({ runestones: data.runestones, count: data.newPrestigeCount, milestoneRunestones: data.milestoneRunestones });
|
||||||
await reload();
|
await reload();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setPrestigeError(err instanceof Error ? err.message : "Prestige failed");
|
setPrestigeError(err instanceof Error ? err.message : "Prestige failed");
|
||||||
@@ -176,6 +176,9 @@ export const PrestigePanel = (): React.JSX.Element => {
|
|||||||
{result && (
|
{result && (
|
||||||
<p className="success">
|
<p className="success">
|
||||||
Ascended to Prestige {result.count}! Earned {formatNumber(result.runestones)} Runestones.
|
Ascended to Prestige {result.count}! Earned {formatNumber(result.runestones)} Runestones.
|
||||||
|
{result.milestoneRunestones > 0 && (
|
||||||
|
<> 🎉 Milestone bonus: +{formatNumber(result.milestoneRunestones)} Runestones!</>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ export interface BossChallengeResponse {
|
|||||||
crystals: number;
|
crystals: number;
|
||||||
upgradeIds: string[];
|
upgradeIds: string[];
|
||||||
equipmentIds: string[];
|
equipmentIds: string[];
|
||||||
|
/** Runestone bounty awarded for defeating this boss for the very first time */
|
||||||
|
bountyRunestones: number;
|
||||||
};
|
};
|
||||||
casualties?: Array<{
|
casualties?: Array<{
|
||||||
adventurerId: string;
|
adventurerId: string;
|
||||||
@@ -72,6 +74,8 @@ export interface PrestigeRequest {
|
|||||||
export interface PrestigeResponse {
|
export interface PrestigeResponse {
|
||||||
runestones: number;
|
runestones: number;
|
||||||
newPrestigeCount: number;
|
newPrestigeCount: number;
|
||||||
|
/** Bonus runestones awarded for reaching a milestone prestige (every 5th), 0 if not a milestone */
|
||||||
|
milestoneRunestones: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BuyPrestigeUpgradeRequest {
|
export interface BuyPrestigeUpgradeRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user