generated from nhcarrigan/template
feat: goddess API routes, services, and tests (chunk 4)
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.
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
/**
|
||||
* @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,
|
||||
};
|
||||
Reference in New Issue
Block a user