feat: gold/sec display with multipliers (#100) and peasant late-game upgrades (#101)

This commit is contained in:
2026-03-23 13:52:02 -07:00
committed by Naomi Carrigan
parent bdb8d4123b
commit 06c80e186a
4 changed files with 110 additions and 1 deletions
+8 -1
View File
@@ -7,7 +7,7 @@
/* eslint-disable max-lines-per-function -- Large header with many resource and action elements */
/* eslint-disable complexity -- Many conditional resource and badge render paths */
import { useGame } from "../../context/gameContext.js";
import { RESOURCE_CAP } from "../../engine/tick.js";
import { RESOURCE_CAP, computeGoldPerSecond } from "../../engine/tick.js";
import type { Resource } from "@elysium/types";
import type { JSX } from "react";
@@ -80,11 +80,13 @@ const ResourceBar = ({
const { formatNumber, syncError, state } = useGame();
const { gold, essence, crystals } = resources;
let partyCombatPower = 0;
let goldPerSecond = 0;
if (state !== null) {
for (const adventurer of state.adventurers) {
const contribution = adventurer.combatPower * adventurer.count;
partyCombatPower = partyCombatPower + contribution;
}
goldPerSecond = computeGoldPerSecond(state);
}
const resourceValues = [ gold, essence, crystals ];
const anyFull = resourceValues.some((v) => {
@@ -113,6 +115,11 @@ const ResourceBar = ({
</span>
: null}
</div>
<div className="resource">
<span className="resource-icon">{"📈"}</span>
<span className="resource-value">{formatNumber(goldPerSecond)}</span>
<span className="resource-label">{"Gold/s"}</span>
</div>
<div className={`resource${essenceFull
? " resource-full"
: ""}`}>
+72
View File
@@ -123,6 +123,78 @@ const capResource = (value: number): number => {
return Math.min(value, RESOURCE_CAP);
};
/**
* Pure function — applies one game tick to the state.
* DeltaSeconds: time elapsed since last tick.
* Returns a new GameState (does not mutate the original).
* @param state - The current game state.
* @param deltaSeconds - Time elapsed since last tick in seconds.
* @returns A new GameState with the tick applied.
*/
/**
* Computes the effective gold earned per second across all adventurers,
* including all active multipliers (upgrades, prestige, equipment, etc.).
* @param state - The current game state.
* @returns Gold per second as a number.
*/
export const computeGoldPerSecond = (state: GameState): number => {
const equippedItems: Array<Equipment> = state.equipment.filter((item) => {
return item.equipped;
});
const equipmentGoldMultiplier = equippedItems.reduce((mult, item) => {
return mult * (item.bonus.goldMultiplier ?? 1);
}, 1);
const setGoldMultiplier = computeSetBonuses(
equippedItems.map((item) => {
return item.id;
}),
EQUIPMENT_SETS,
).goldMultiplier;
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
const echoIncome = state.transcendence?.echoIncomeMultiplier ?? 1;
const craftedGoldMultiplier = state.exploration?.craftedGoldMultiplier ?? 1;
const companionBonus = getActiveCompanionBonus(
state.companions?.activeCompanionId,
state.companions?.unlockedCompanionIds ?? [],
);
const companionGoldMult
= companionBonus?.type === "passiveGold"
? 1 + companionBonus.value
: 1;
let goldPerSecond = 0;
for (const adventurer of state.adventurers) {
if (!adventurer.unlocked || adventurer.count === 0) {
continue;
}
const upgradeMultiplier = state.upgrades.
filter((upgrade) => {
const isGlobal = upgrade.target === "global";
const isThisAdventurer
= upgrade.target === "adventurer"
&& upgrade.adventurerId === adventurer.id;
return upgrade.purchased && (isGlobal || isThisAdventurer);
}).
reduce((mult, upgrade) => {
return mult * upgrade.multiplier;
}, 1);
const contribution
= adventurer.goldPerSecond
* adventurer.count
* upgradeMultiplier
* state.prestige.productionMultiplier
* runestonesIncome
* echoIncome
* equipmentGoldMultiplier
* setGoldMultiplier
* craftedGoldMultiplier
* companionGoldMult;
goldPerSecond = goldPerSecond + contribution;
}
return goldPerSecond;
};
/**
* Pure function — applies one game tick to the state.
* DeltaSeconds: time elapsed since last tick.