generated from nhcarrigan/template
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
8a332dc9ce
|
|||
|
56d963dc90
|
|||
|
77c7ee02a6
|
@@ -628,7 +628,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Prism Golem",
|
name: "The Prism Golem",
|
||||||
prestigeRequirement: 3,
|
prestigeRequirement: 3,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "crystal_sage_1" ],
|
||||||
zoneId: "crystalline_spire",
|
zoneId: "crystalline_spire",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -664,7 +664,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Faceted",
|
name: "The Faceted",
|
||||||
prestigeRequirement: 4,
|
prestigeRequirement: 4,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "void_sentinel_1" ],
|
||||||
zoneId: "crystalline_spire",
|
zoneId: "crystalline_spire",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -682,7 +682,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Diamond Colossus",
|
name: "The Diamond Colossus",
|
||||||
prestigeRequirement: 4,
|
prestigeRequirement: 4,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "eternal_champion_1" ],
|
||||||
zoneId: "crystalline_spire",
|
zoneId: "crystalline_spire",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -700,7 +700,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Crystal Sovereign",
|
name: "The Crystal Sovereign",
|
||||||
prestigeRequirement: 4,
|
prestigeRequirement: 4,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "cosmos_knight_1" ],
|
||||||
zoneId: "crystalline_spire",
|
zoneId: "crystalline_spire",
|
||||||
},
|
},
|
||||||
// ── Void Sanctum ──────────────────────────────────────────────────────────
|
// ── Void Sanctum ──────────────────────────────────────────────────────────
|
||||||
@@ -719,7 +719,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Void Herald",
|
name: "The Void Herald",
|
||||||
prestigeRequirement: 4,
|
prestigeRequirement: 4,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "seraph_knight_1" ],
|
||||||
zoneId: "void_sanctum",
|
zoneId: "void_sanctum",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -755,7 +755,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Unmaker",
|
name: "The Unmaker",
|
||||||
prestigeRequirement: 5,
|
prestigeRequirement: 5,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "abyss_diver_1" ],
|
||||||
zoneId: "void_sanctum",
|
zoneId: "void_sanctum",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -791,7 +791,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Void Emperor",
|
name: "The Void Emperor",
|
||||||
prestigeRequirement: 5,
|
prestigeRequirement: 5,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "infernal_warden_1" ],
|
||||||
zoneId: "void_sanctum",
|
zoneId: "void_sanctum",
|
||||||
},
|
},
|
||||||
// ── Eternal Throne ────────────────────────────────────────────────────────
|
// ── Eternal Throne ────────────────────────────────────────────────────────
|
||||||
@@ -810,7 +810,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Throne Warden",
|
name: "The Throne Warden",
|
||||||
prestigeRequirement: 5,
|
prestigeRequirement: 5,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "infinity_ranger_1" ],
|
||||||
zoneId: "eternal_throne",
|
zoneId: "eternal_throne",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -846,7 +846,7 @@ export const defaultBosses: Array<Boss> = [
|
|||||||
name: "The Undying",
|
name: "The Undying",
|
||||||
prestigeRequirement: 5,
|
prestigeRequirement: 5,
|
||||||
status: "locked",
|
status: "locked",
|
||||||
upgradeRewards: [],
|
upgradeRewards: [ "reality_warden_1" ],
|
||||||
zoneId: "eternal_throne",
|
zoneId: "eternal_throne",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
/* eslint-disable complexity -- Complex component with many render paths */
|
/* eslint-disable complexity -- Complex component with many render paths */
|
||||||
import { type JSX, useState } from "react";
|
import { type JSX, useState } from "react";
|
||||||
import { useGame } from "../../context/gameContext.js";
|
import { useGame } from "../../context/gameContext.js";
|
||||||
|
import { computeEffectiveAdventurerStats } from "../../engine/tick.js";
|
||||||
import { cdnImage } from "../../utils/cdn.js";
|
import { cdnImage } from "../../utils/cdn.js";
|
||||||
import { LockToggle } from "../ui/lockToggle.js";
|
import { LockToggle } from "../ui/lockToggle.js";
|
||||||
import type { Adventurer } from "@elysium/types";
|
import type { Adventurer } from "@elysium/types";
|
||||||
@@ -76,12 +77,19 @@ const computeMaxAffordable = (adventurer: Adventurer, gold: number): number => {
|
|||||||
return quantity;
|
return quantity;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface EffectiveAdventurerStats {
|
||||||
|
readonly combatPower: number;
|
||||||
|
readonly essencePerSecond: number;
|
||||||
|
readonly goldPerSecond: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface AdventurerCardProperties {
|
interface AdventurerCardProperties {
|
||||||
readonly adventurer: Adventurer;
|
readonly adventurer: Adventurer;
|
||||||
readonly currentGold: number;
|
readonly currentGold: number;
|
||||||
readonly batchSize: BatchSize;
|
readonly batchSize: BatchSize;
|
||||||
readonly unlockHint: string | undefined;
|
readonly unlockHint: string | undefined;
|
||||||
readonly formatNumber: (n: number)=> string;
|
readonly formatNumber: (n: number)=> string;
|
||||||
|
readonly effectiveStats: EffectiveAdventurerStats;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,6 +100,7 @@ interface AdventurerCardProperties {
|
|||||||
* @param props.batchSize - The selected batch size.
|
* @param props.batchSize - The selected batch size.
|
||||||
* @param props.unlockHint - Optional quest name that unlocks this adventurer.
|
* @param props.unlockHint - Optional quest name that unlocks this adventurer.
|
||||||
* @param props.formatNumber - The number formatting utility function.
|
* @param props.formatNumber - The number formatting utility function.
|
||||||
|
* @param props.effectiveStats - The post-multiplier per-unit stats.
|
||||||
* @returns The JSX element.
|
* @returns The JSX element.
|
||||||
*/
|
*/
|
||||||
const AdventurerCard = ({
|
const AdventurerCard = ({
|
||||||
@@ -100,6 +109,7 @@ const AdventurerCard = ({
|
|||||||
batchSize,
|
batchSize,
|
||||||
unlockHint,
|
unlockHint,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
|
effectiveStats,
|
||||||
}: AdventurerCardProperties): JSX.Element => {
|
}: AdventurerCardProperties): JSX.Element => {
|
||||||
const { buyAdventurer } = useGame();
|
const { buyAdventurer } = useGame();
|
||||||
|
|
||||||
@@ -134,17 +144,17 @@ const AdventurerCard = ({
|
|||||||
<div className="adventurer-info">
|
<div className="adventurer-info">
|
||||||
<h3>{adventurer.name}</h3>
|
<h3>{adventurer.name}</h3>
|
||||||
<p>
|
<p>
|
||||||
{formatNumber(adventurer.goldPerSecond)}
|
{formatNumber(effectiveStats.goldPerSecond)}
|
||||||
{" gold/s each"}
|
{" gold/s each"}
|
||||||
</p>
|
</p>
|
||||||
{adventurer.essencePerSecond > 0
|
{adventurer.essencePerSecond > 0
|
||||||
&& <p>
|
&& <p>
|
||||||
{formatNumber(adventurer.essencePerSecond)}
|
{formatNumber(effectiveStats.essencePerSecond)}
|
||||||
{" essence/s each"}
|
{" essence/s each"}
|
||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
<p>
|
<p>
|
||||||
{formatNumber(adventurer.combatPower)}
|
{formatNumber(effectiveStats.combatPower)}
|
||||||
{" combat power each"}
|
{" combat power each"}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -280,6 +290,10 @@ const AdventurerPanel = (): JSX.Element => {
|
|||||||
adventurer={adventurer}
|
adventurer={adventurer}
|
||||||
batchSize={batchSize}
|
batchSize={batchSize}
|
||||||
currentGold={state.resources.gold}
|
currentGold={state.resources.gold}
|
||||||
|
effectiveStats={computeEffectiveAdventurerStats(
|
||||||
|
state,
|
||||||
|
adventurer.id,
|
||||||
|
)}
|
||||||
formatNumber={formatNumber}
|
formatNumber={formatNumber}
|
||||||
key={adventurer.id}
|
key={adventurer.id}
|
||||||
unlockHint={adventurerUnlockHints.get(adventurer.id)}
|
unlockHint={adventurerUnlockHints.get(adventurer.id)}
|
||||||
|
|||||||
+120
-1
@@ -243,10 +243,129 @@ export const computeEssencePerSecond = (state: GameState): number => {
|
|||||||
return essencePerSecond;
|
return essencePerSecond;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the effective per-unit stats for a single adventurer type,
|
||||||
|
* applying all active multipliers (upgrades, prestige, equipment, echo,
|
||||||
|
* crafted, companion). The returned values represent what a single
|
||||||
|
* adventurer of this type currently contributes per second, matching the
|
||||||
|
* per-unit contribution used by computeGoldPerSecond and
|
||||||
|
* computeEssencePerSecond.
|
||||||
|
* @param state - The current game state.
|
||||||
|
* @param adventurerId - The ID of the adventurer to compute stats for.
|
||||||
|
* @returns Effective per-unit goldPerSecond, essencePerSecond, and combatPower.
|
||||||
|
*/
|
||||||
|
export const computeEffectiveAdventurerStats = (
|
||||||
|
state: GameState,
|
||||||
|
adventurerId: string,
|
||||||
|
): { combatPower: number; essencePerSecond: number; goldPerSecond: number } => {
|
||||||
|
const adventurer = state.adventurers.find((a) => {
|
||||||
|
return a.id === adventurerId;
|
||||||
|
});
|
||||||
|
|
||||||
|
/* V8 ignore next 3 -- @preserve */
|
||||||
|
if (adventurer === undefined) {
|
||||||
|
return { combatPower: 0, essencePerSecond: 0, goldPerSecond: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const upgradeMultiplier = state.upgrades.
|
||||||
|
filter((upgrade) => {
|
||||||
|
const isGlobal = upgrade.target === "global";
|
||||||
|
const isThisAdventurer
|
||||||
|
= upgrade.target === "adventurer"
|
||||||
|
&& upgrade.adventurerId === adventurerId;
|
||||||
|
return upgrade.purchased && (isGlobal || isThisAdventurer);
|
||||||
|
}).
|
||||||
|
reduce((mult, upgrade) => {
|
||||||
|
return mult * upgrade.multiplier;
|
||||||
|
}, 1);
|
||||||
|
|
||||||
|
const equippedItems = state.equipment.filter((item) => {
|
||||||
|
return item.equipped;
|
||||||
|
});
|
||||||
|
const equipmentGoldMultiplier = equippedItems.reduce((mult, item) => {
|
||||||
|
return mult * (item.bonus.goldMultiplier ?? 1);
|
||||||
|
}, 1);
|
||||||
|
const equipmentCombatMultiplier = equippedItems.reduce((mult, item) => {
|
||||||
|
return mult * (item.bonus.combatMultiplier ?? 1);
|
||||||
|
}, 1);
|
||||||
|
const equippedItemIds = equippedItems.map((item) => {
|
||||||
|
return item.id;
|
||||||
|
});
|
||||||
|
const setBonuses = computeSetBonuses(equippedItemIds, EQUIPMENT_SETS);
|
||||||
|
|
||||||
|
const runestonesIncome = state.prestige.runestonesIncomeMultiplier ?? 1;
|
||||||
|
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
|
||||||
|
// eslint-disable-next-line stylistic/no-mixed-operators -- prestige count * factor is clear
|
||||||
|
const prestigeCombatMultiplier = 1 + state.prestige.count * 0.1;
|
||||||
|
const echoIncome = state.transcendence?.echoIncomeMultiplier ?? 1;
|
||||||
|
const echoCombatMultiplier = state.transcendence?.echoCombatMultiplier ?? 1;
|
||||||
|
const craftedGoldMultiplier
|
||||||
|
= state.exploration?.craftedGoldMultiplier ?? 1;
|
||||||
|
const craftedEssenceMultiplier
|
||||||
|
= state.exploration?.craftedEssenceMultiplier ?? 1;
|
||||||
|
const craftedCombatMultiplier
|
||||||
|
= state.exploration?.craftedCombatMultiplier ?? 1;
|
||||||
|
|
||||||
|
const companionBonus = getActiveCompanionBonus(
|
||||||
|
state.companions?.activeCompanionId,
|
||||||
|
state.companions?.unlockedCompanionIds ?? [],
|
||||||
|
);
|
||||||
|
const companionGoldMult
|
||||||
|
= companionBonus?.type === "passiveGold"
|
||||||
|
? 1 + companionBonus.value
|
||||||
|
: 1;
|
||||||
|
const companionEssenceMult
|
||||||
|
= companionBonus?.type === "essenceIncome"
|
||||||
|
? 1 + companionBonus.value
|
||||||
|
: 1;
|
||||||
|
const companionCombatMult
|
||||||
|
= companionBonus?.type === "bossDamage"
|
||||||
|
? 1 + companionBonus.value
|
||||||
|
: 1;
|
||||||
|
|
||||||
|
const goldPerSecond
|
||||||
|
= adventurer.goldPerSecond
|
||||||
|
* upgradeMultiplier
|
||||||
|
* state.prestige.productionMultiplier
|
||||||
|
* runestonesIncome
|
||||||
|
* echoIncome
|
||||||
|
* equipmentGoldMultiplier
|
||||||
|
* setBonuses.goldMultiplier
|
||||||
|
* craftedGoldMultiplier
|
||||||
|
* companionGoldMult;
|
||||||
|
|
||||||
|
const essencePerSecond
|
||||||
|
= adventurer.essencePerSecond
|
||||||
|
* upgradeMultiplier
|
||||||
|
* state.prestige.productionMultiplier
|
||||||
|
* runestonesEssence
|
||||||
|
* craftedEssenceMultiplier
|
||||||
|
* companionEssenceMult;
|
||||||
|
|
||||||
|
const combatPower
|
||||||
|
= adventurer.combatPower
|
||||||
|
* upgradeMultiplier
|
||||||
|
* prestigeCombatMultiplier
|
||||||
|
* equipmentCombatMultiplier
|
||||||
|
* setBonuses.combatMultiplier
|
||||||
|
* echoCombatMultiplier
|
||||||
|
* craftedCombatMultiplier
|
||||||
|
* companionCombatMult;
|
||||||
|
|
||||||
|
return { combatPower, essencePerSecond, goldPerSecond };
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes the party's total combat power, applying all active multipliers
|
* Computes the party's total combat power, applying all active multipliers
|
||||||
* (upgrades, prestige, equipment, set bonuses, echo, crafted, companion).
|
* (upgrades, prestige, equipment, set bonuses, echo, crafted, companion).
|
||||||
* This mirrors the server-side calculatePartyStats in boss.ts.
|
* This mirrors the server-side calculatePartyStats in boss.ts and is the
|
||||||
|
* single source of truth for all combat-power checks in the client:
|
||||||
|
* - Displayed as "Combat Power" in the resource bar
|
||||||
|
* - Displayed as "Party DPS" in the boss panel
|
||||||
|
* - Used to gate quest availability
|
||||||
|
* Note: the active companion's bossDamage bonus is intentionally included
|
||||||
|
* here, as it applies to the full combat power calculation (boss fights and
|
||||||
|
* quest gating alike), matching the server-side behaviour.
|
||||||
* @param state - The current game state.
|
* @param state - The current game state.
|
||||||
* @returns The total party combat power.
|
* @returns The total party combat power.
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user