generated from nhcarrigan/template
0d36b255ee
Add six new goddess-mode API routes (boss fight, consecration, enlightenment, upgrade purchase, crafting, exploration) alongside matching service modules and full test suites at 100% coverage.
202 lines
6.9 KiB
TypeScript
202 lines
6.9 KiB
TypeScript
/**
|
|
* @file Consecration service handling eligibility checks and post-consecration state building.
|
|
* @copyright nhcarrigan
|
|
* @license Naomi's Public License
|
|
* @author Naomi Carrigan
|
|
*/
|
|
/* eslint-disable max-lines-per-function -- Function requires many steps */
|
|
/* eslint-disable stylistic/max-len -- Service logic requires long lines */
|
|
import { defaultConsecrationUpgrades } from "../data/goddessConsecrationUpgrades.js";
|
|
import { initialGoddessState } from "../data/initialState.js";
|
|
import type { ConsecrationData, GameState } from "@elysium/types";
|
|
|
|
/**
|
|
* Base prayers threshold for the first consecration.
|
|
*/
|
|
const baseConsecrationThreshold = 50_000;
|
|
|
|
/**
|
|
* Divisor used in the divinity yield formula.
|
|
*/
|
|
const divinityYieldDivisor = 1000;
|
|
|
|
/**
|
|
* Calculates the prayers threshold required for the next consecration.
|
|
* Formula: BASE * (count + 1)^2 * thresholdMultiplier.
|
|
* @param consecrationCount - The number of consecrations completed so far.
|
|
* @param thresholdMultiplier - An optional stardust-upgrade multiplier applied to the threshold.
|
|
* @returns The prayers amount required to consecrate.
|
|
*/
|
|
const calculateConsecrationThreshold = (
|
|
consecrationCount: number,
|
|
thresholdMultiplier = 1,
|
|
): number => {
|
|
return (
|
|
baseConsecrationThreshold
|
|
* Math.pow(consecrationCount + 1, 2)
|
|
* thresholdMultiplier
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Returns true if the player is eligible to consecrate:
|
|
* the total prayers earned in the current run must meet the threshold.
|
|
* @param state - The current game state.
|
|
* @returns Whether the player is eligible for consecration.
|
|
*/
|
|
const isEligibleForConsecration = (state: GameState): boolean => {
|
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
|
/* v8 ignore next 3 -- @preserve */
|
|
if (state.goddess === undefined) {
|
|
return false;
|
|
}
|
|
const thresholdMultiplier
|
|
= state.goddess.enlightenment.stardustConsecrationThresholdMultiplier;
|
|
const threshold = calculateConsecrationThreshold(
|
|
state.goddess.consecration.count,
|
|
thresholdMultiplier,
|
|
);
|
|
return state.goddess.totalPrayersEarned >= threshold;
|
|
};
|
|
|
|
/**
|
|
* Calculates the divinity yield from a consecration.
|
|
* Formula: MAX(1, FLOOR(SQRT(totalPrayersEarned / divisor) * divinityMultiplier)).
|
|
* @param totalPrayersEarned - Total prayers earned in the current consecration run.
|
|
* @param divinityMultiplier - Multiplier from stardust upgrades applied to divinity yield.
|
|
* @returns The divinity earned.
|
|
*/
|
|
const calculateDivinityYield = (
|
|
totalPrayersEarned: number,
|
|
divinityMultiplier: number,
|
|
): number => {
|
|
return Math.max(
|
|
1,
|
|
Math.floor(
|
|
Math.sqrt(totalPrayersEarned / divinityYieldDivisor) * divinityMultiplier,
|
|
),
|
|
);
|
|
};
|
|
|
|
/**
|
|
* Computes the consecration production multiplier from the count.
|
|
* Each consecration adds 25% to the production multiplier.
|
|
* @param count - The number of consecrations completed.
|
|
* @returns The computed production multiplier as a number.
|
|
*/
|
|
const computeConsecrationProductionMultiplier = (count: number): number => {
|
|
const bonus = count * 0.25;
|
|
return 1 + bonus;
|
|
};
|
|
|
|
const getCategoryMultiplier = (
|
|
purchasedUpgradeIds: Array<string>,
|
|
category: string,
|
|
): number => {
|
|
return defaultConsecrationUpgrades.filter((upgrade) => {
|
|
return (
|
|
upgrade.category === category
|
|
&& purchasedUpgradeIds.includes(upgrade.id)
|
|
);
|
|
}).reduce((mult, upgrade) => {
|
|
return mult * upgrade.multiplier;
|
|
}, 1);
|
|
};
|
|
|
|
/**
|
|
* Computes all three divinity-upgrade multipliers from the purchased upgrade IDs.
|
|
* @param purchasedUpgradeIds - The array of purchased consecration upgrade IDs.
|
|
* @returns An object containing the three divinity multiplier values.
|
|
*/
|
|
const computeConsecrationDivinityMultipliers = (
|
|
purchasedUpgradeIds: Array<string>,
|
|
): Pick<
|
|
ConsecrationData,
|
|
| "divinityCombatMultiplier"
|
|
| "divinityDisciplesMultiplier"
|
|
| "divinityPrayersMultiplier"
|
|
> => {
|
|
return {
|
|
divinityCombatMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "combat"),
|
|
divinityDisciplesMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "disciples"),
|
|
divinityPrayersMultiplier: getCategoryMultiplier(purchasedUpgradeIds, "prayers"),
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Builds the updated goddess state after a consecration reset.
|
|
* Resets the current run (bosses, quests, disciples, upgrades, zones, exploration crafting).
|
|
* Preserves: equipment, achievements, consecration data (updated), enlightenment, lifetime stats, sacred materials.
|
|
* @param state - The current game state before consecration.
|
|
* @returns The divinity earned and the updated goddess state.
|
|
*/
|
|
const buildPostConsecrationState = (
|
|
state: GameState,
|
|
): { divinityEarned: number; updatedGoddess: NonNullable<GameState["goddess"]> } => {
|
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Caller must ensure goddess exists
|
|
const goddess = state.goddess as NonNullable<GameState["goddess"]>;
|
|
|
|
const divinityMultiplier
|
|
= goddess.enlightenment.stardustConsecrationDivinityMultiplier;
|
|
const divinityEarned = calculateDivinityYield(
|
|
goddess.totalPrayersEarned,
|
|
divinityMultiplier,
|
|
);
|
|
|
|
const updatedCount = goddess.consecration.count + 1;
|
|
const updatedDivinity = goddess.consecration.divinity + divinityEarned;
|
|
const productionMultiplier = computeConsecrationProductionMultiplier(updatedCount);
|
|
|
|
const updatedConsecration: ConsecrationData = {
|
|
...goddess.consecration,
|
|
count: updatedCount,
|
|
divinity: updatedDivinity,
|
|
lastConsecratedAt: Date.now(),
|
|
productionMultiplier: productionMultiplier,
|
|
...computeConsecrationDivinityMultipliers(
|
|
goddess.consecration.purchasedUpgradeIds,
|
|
),
|
|
};
|
|
|
|
const freshGoddess = initialGoddessState();
|
|
|
|
const updatedGoddess: NonNullable<GameState["goddess"]> = {
|
|
...freshGoddess,
|
|
achievements: goddess.achievements,
|
|
bosses: freshGoddess.bosses.map((b) => {
|
|
// eslint-disable-next-line capitalized-comments -- v8 ignore
|
|
/* v8 ignore next 7 -- @preserve */
|
|
const existing = goddess.bosses.find((gb) => {
|
|
return gb.id === b.id;
|
|
});
|
|
return {
|
|
...b,
|
|
bountyDivinityClaimed: existing?.bountyDivinityClaimed ?? false,
|
|
};
|
|
}),
|
|
consecration: updatedConsecration,
|
|
enlightenment: goddess.enlightenment,
|
|
equipment: goddess.equipment,
|
|
exploration: {
|
|
...freshGoddess.exploration,
|
|
materials: goddess.exploration.materials,
|
|
},
|
|
lastTickAt: Date.now(),
|
|
lifetimeBossesDefeated: goddess.lifetimeBossesDefeated,
|
|
lifetimePrayersEarned: goddess.lifetimePrayersEarned + goddess.totalPrayersEarned,
|
|
lifetimeQuestsCompleted: goddess.lifetimeQuestsCompleted,
|
|
totalPrayersEarned: 0,
|
|
};
|
|
|
|
return { divinityEarned, updatedGoddess };
|
|
};
|
|
|
|
export {
|
|
buildPostConsecrationState,
|
|
calculateConsecrationThreshold,
|
|
calculateDivinityYield,
|
|
computeConsecrationDivinityMultipliers,
|
|
computeConsecrationProductionMultiplier,
|
|
isEligibleForConsecration,
|
|
};
|