fix: resolve sync inflation, signature mismatch, CP accuracy, auto-buy cap, unlock hints
Security Scan and Upload / Security & DefectDojo Upload (pull_request) Successful in 1m3s
CI / Lint, Build & Test (pull_request) Failing after 1m8s

- #147: Guard all patch functions with hasChanged before incrementing
  sync counter to prevent inflation on no-op patches
- #148: Clear stale HMAC signature after each boss fight so subsequent
  auto-saves do not send a mismatched signature
- #146: Auto-unlock adventurer-specific upgrades in applyTick when
  their adventurer count > 0; show recruit hint in upgrade panel
- #149: Add Essence/s row to resource bar dropdown
- #150: Fix broken auto-quest CP reduce formula; centralise via
  computePartyCombatPower which applies all multipliers correctly
- #151: Cap auto-buy at 100 for non-max-tier adventurers; max tier
  (highest level unlocked) remains uncapped
- #152: Export computePartyCombatPower from tick, applying global
  upgrades, prestige, equipment, set bonuses, echo, crafted, and
  companion multipliers; use it in resource bar and boss panel
This commit is contained in:
2026-03-25 16:47:53 -07:00
committed by Naomi Carrigan
parent b6e218167d
commit 4c297f1ce1
6 changed files with 315 additions and 88 deletions
+16 -69
View File
@@ -11,10 +11,11 @@
/* eslint-disable max-lines -- Boss panel with sub-component and helper function */
import { type JSX, useState } from "react";
import { useGame } from "../../context/gameContext.js";
import { computePartyCombatPower } from "../../engine/tick.js";
import { cdnImage } from "../../utils/cdn.js";
import { LockToggle } from "../ui/lockToggle.js";
import { ZoneSelector } from "./zoneSelector.js";
import type { Boss, GameState } from "@elysium/types";
import type { Boss } from "@elysium/types";
interface BossCardProperties {
readonly boss: Boss;
@@ -157,72 +158,6 @@ const BossCard = ({
);
};
/**
* Computes party DPS and HP from the current game state.
* @param state - The full game state.
* @returns The computed party DPS and HP values.
*/
const computePartyStats = (
state: GameState,
): {
partyDps: number;
partyHp: number;
} => {
const { upgrades, adventurers, equipment, prestige } = state;
let globalMultiplier = 1;
for (const upgrade of upgrades) {
const { purchased, target, multiplier } = upgrade;
if (purchased && target === "global") {
globalMultiplier = globalMultiplier * multiplier;
}
}
const prestigeBonus = prestige.count * 0.1;
const prestigeMultiplier = 1 + prestigeBonus;
const equipmentCombatMultiplier = equipment.
filter((item) => {
return item.equipped && item.bonus.combatMultiplier !== undefined;
}).
reduce((multiplier, item) => {
return multiplier * (item.bonus.combatMultiplier ?? 1);
}, 1);
let partyDps = 0;
let partyHp = 0;
for (const adventurer of adventurers) {
const { count, id: adventurerId, combatPower, level } = adventurer;
if (count === 0) {
continue;
}
let adventurerMultiplier = 1;
for (const upgrade of upgrades) {
const {
purchased,
target,
multiplier,
adventurerId: upgradeAdventurerId,
} = upgrade;
if (
purchased
&& target === "adventurer"
&& upgradeAdventurerId === adventurerId
) {
adventurerMultiplier = adventurerMultiplier * multiplier;
}
}
const dps
= combatPower
* count
* adventurerMultiplier
* globalMultiplier
* prestigeMultiplier;
partyDps = partyDps + dps;
const hp = level * 50 * count;
partyHp = partyHp + hp;
}
partyDps = partyDps * equipmentCombatMultiplier;
return { partyDps, partyHp };
};
/**
* Renders the boss panel with zone selection and boss list.
* @returns The JSX element.
@@ -266,7 +201,14 @@ const BossPanel = (): JSX.Element => {
void handleChallenge(bossId);
}
const { zones, bosses, quests, autoBoss, prestige: playerPrestige } = state;
const {
adventurers,
autoBoss,
bosses,
prestige: playerPrestige,
quests,
zones,
} = state;
const activeZone = zones.find((zone) => {
return zone.id === activeZoneId;
@@ -349,7 +291,12 @@ const BossPanel = (): JSX.Element => {
}
const autoBossOn = autoBoss === true;
const { partyDps, partyHp } = computePartyStats(state);
const partyDps = computePartyCombatPower(state);
let partyHp = 0;
for (const { level, count } of adventurers) {
// eslint-disable-next-line stylistic/no-mixed-operators -- level * 50 * count is clear
partyHp = partyHp + level * 50 * count;
}
const { count: prestigeCount } = playerPrestige;
return (