generated from nhcarrigan/template
fix: resolve sync inflation, signature mismatch, CP accuracy, auto-buy cap, unlock hints
- #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:
@@ -195,6 +195,138 @@ export const computeGoldPerSecond = (state: GameState): number => {
|
||||
return goldPerSecond;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the current essence per second for the given game state,
|
||||
* applying all relevant multipliers (upgrades, prestige, echo, crafted, companion).
|
||||
* @param state - The current game state.
|
||||
* @returns The total essence per second.
|
||||
*/
|
||||
export const computeEssencePerSecond = (state: GameState): number => {
|
||||
const runestonesEssence = state.prestige.runestonesEssenceMultiplier ?? 1;
|
||||
const craftedEssenceMultiplier
|
||||
= state.exploration?.craftedEssenceMultiplier ?? 1;
|
||||
const companionBonus = getActiveCompanionBonus(
|
||||
state.companions?.activeCompanionId,
|
||||
state.companions?.unlockedCompanionIds ?? [],
|
||||
);
|
||||
const companionEssenceMult
|
||||
= companionBonus?.type === "essenceIncome"
|
||||
? 1 + companionBonus.value
|
||||
: 1;
|
||||
|
||||
let essencePerSecond = 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.essencePerSecond
|
||||
* adventurer.count
|
||||
* upgradeMultiplier
|
||||
* state.prestige.productionMultiplier
|
||||
* runestonesEssence
|
||||
* craftedEssenceMultiplier
|
||||
* companionEssenceMult;
|
||||
essencePerSecond = essencePerSecond + contribution;
|
||||
}
|
||||
return essencePerSecond;
|
||||
};
|
||||
|
||||
/**
|
||||
* Computes the party's total combat power, applying all active multipliers
|
||||
* (upgrades, prestige, equipment, set bonuses, echo, crafted, companion).
|
||||
* This mirrors the server-side calculatePartyStats in boss.ts.
|
||||
* @param state - The current game state.
|
||||
* @returns The total party combat power.
|
||||
*/
|
||||
export const computePartyCombatPower = (state: GameState): number => {
|
||||
let globalMultiplier = 1;
|
||||
for (const upgrade of state.upgrades) {
|
||||
if (upgrade.purchased && upgrade.target === "global") {
|
||||
globalMultiplier = globalMultiplier * upgrade.multiplier;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line stylistic/no-mixed-operators -- prestige count * factor is clear
|
||||
const prestigeMultiplier = 1 + state.prestige.count * 0.1;
|
||||
|
||||
const equipmentCombatMultiplier = state.equipment.
|
||||
filter((item) => {
|
||||
return item.equipped && item.bonus.combatMultiplier !== undefined;
|
||||
}).
|
||||
reduce((mult, item) => {
|
||||
return mult * (item.bonus.combatMultiplier ?? 1);
|
||||
}, 1);
|
||||
|
||||
const equippedItemIds = state.equipment.
|
||||
filter((item) => {
|
||||
return item.equipped;
|
||||
}).
|
||||
map((item) => {
|
||||
return item.id;
|
||||
});
|
||||
const { combatMultiplier: setCombatMultiplier } = computeSetBonuses(
|
||||
equippedItemIds,
|
||||
EQUIPMENT_SETS,
|
||||
);
|
||||
|
||||
const echoCombatMultiplier
|
||||
= state.transcendence?.echoCombatMultiplier ?? 1;
|
||||
const craftedCombatMultiplier
|
||||
= state.exploration?.craftedCombatMultiplier ?? 1;
|
||||
|
||||
const companionBonus = getActiveCompanionBonus(
|
||||
state.companions?.activeCompanionId,
|
||||
state.companions?.unlockedCompanionIds ?? [],
|
||||
);
|
||||
const companionCombatMult
|
||||
= companionBonus?.type === "bossDamage"
|
||||
? 1 + companionBonus.value
|
||||
: 1;
|
||||
|
||||
let partyCombatPower = 0;
|
||||
for (const adventurer of state.adventurers) {
|
||||
if (adventurer.count === 0) {
|
||||
continue;
|
||||
}
|
||||
let adventurerMultiplier = 1;
|
||||
for (const upgrade of state.upgrades) {
|
||||
if (
|
||||
upgrade.purchased
|
||||
&& upgrade.target === "adventurer"
|
||||
&& upgrade.adventurerId === adventurer.id
|
||||
) {
|
||||
adventurerMultiplier = adventurerMultiplier * upgrade.multiplier;
|
||||
}
|
||||
}
|
||||
const contribution
|
||||
= adventurer.combatPower
|
||||
* adventurer.count
|
||||
* adventurerMultiplier
|
||||
* globalMultiplier
|
||||
* prestigeMultiplier;
|
||||
partyCombatPower = partyCombatPower + contribution;
|
||||
}
|
||||
|
||||
return partyCombatPower
|
||||
* equipmentCombatMultiplier
|
||||
* setCombatMultiplier
|
||||
* echoCombatMultiplier
|
||||
* craftedCombatMultiplier
|
||||
* companionCombatMult;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pure function — applies one game tick to the state.
|
||||
* DeltaSeconds: time elapsed since last tick.
|
||||
@@ -469,6 +601,19 @@ export const applyTick = (
|
||||
challengeCrystals = result.crystalsAwarded;
|
||||
}
|
||||
|
||||
// Auto-unlock adventurer-specific upgrades when their adventurer is recruited
|
||||
updatedUpgrades = updatedUpgrades.map((upgrade) => {
|
||||
if (upgrade.unlocked || upgrade.adventurerId === undefined) {
|
||||
return upgrade;
|
||||
}
|
||||
const adventurer = updatedAdventurers.find((a) => {
|
||||
return a.id === upgrade.adventurerId;
|
||||
});
|
||||
return adventurer !== undefined && adventurer.count > 0
|
||||
? { ...upgrade, unlocked: true }
|
||||
: upgrade;
|
||||
});
|
||||
|
||||
const goldValue = capResource(state.resources.gold + goldGained + questGold);
|
||||
const essenceValue = capResource(
|
||||
state.resources.essence + essenceGained + questEssence,
|
||||
|
||||
Reference in New Issue
Block a user