91 lines
2.7 KiB
JavaScript
91 lines
2.7 KiB
JavaScript
/** Base simultaneous opens at key 1 (x10). Each +1 key adds +1 open until x20, then continues +1 per key. */
|
||
export const BASE_MAX_OPENS = 10;
|
||
|
||
/** Keys 1–10 → next key always costs this many opens. */
|
||
export const OPENS_PER_KEY_EARLY = 100;
|
||
|
||
/** From key 11 onward: cost = OPENS_PER_KEY_EARLY × (keys - 9). */
|
||
export const ESCALATION_KEY_THRESHOLD = 11;
|
||
|
||
export function maxOpensForKeys(keys, openBonus = 0) {
|
||
const k = Math.max(1, keys);
|
||
const bonus = Math.max(0, Math.floor(Number(openBonus) || 0));
|
||
return k + (BASE_MAX_OPENS - 1) + bonus;
|
||
}
|
||
|
||
export function opensRequiredForNextKey(keys, keyOpenReduction = 0) {
|
||
const k = Math.max(1, keys);
|
||
const base =
|
||
k < ESCALATION_KEY_THRESHOLD
|
||
? OPENS_PER_KEY_EARLY
|
||
: OPENS_PER_KEY_EARLY * (k - (ESCALATION_KEY_THRESHOLD - 2));
|
||
const reduction = Math.max(0, Number(keyOpenReduction) || 0);
|
||
return Math.max(1, base - reduction);
|
||
}
|
||
|
||
export function serializeKeyProgress(progress, keyOpenReduction = 0, openBonus = 0) {
|
||
const keys = progress?.keys ?? 1;
|
||
const opensSinceLastKey = progress?.opensSinceLastKey ?? 0;
|
||
const totalOpens = progress?.totalOpens ?? 0;
|
||
const opensRequired = opensRequiredForNextKey(keys, keyOpenReduction);
|
||
return {
|
||
keys,
|
||
maxOpens: maxOpensForKeys(keys, openBonus),
|
||
opensSinceLastKey,
|
||
opensRequired,
|
||
totalOpens,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Apply opens to progress state. Returns updated fields + keysGained.
|
||
*/
|
||
export function applyOpens(progress, count, keyOpenReduction = 0) {
|
||
let keys = Math.max(1, progress.keys ?? 1);
|
||
let opensSinceLastKey = progress.opensSinceLastKey ?? 0;
|
||
let totalOpens = progress.totalOpens ?? 0;
|
||
let keysGained = 0;
|
||
|
||
opensSinceLastKey += count;
|
||
totalOpens += count;
|
||
|
||
while (opensSinceLastKey >= opensRequiredForNextKey(keys, keyOpenReduction)) {
|
||
opensSinceLastKey -= opensRequiredForNextKey(keys, keyOpenReduction);
|
||
keys += 1;
|
||
keysGained += 1;
|
||
}
|
||
|
||
return { keys, opensSinceLastKey, totalOpens, keysGained };
|
||
}
|
||
|
||
/**
|
||
* Rebuild keys/opensSinceLastKey from a lifetime open count (backfill).
|
||
* Prestige reduction is ignored here — backfill uses base costs.
|
||
*/
|
||
export function progressFromTotalOpens(totalOpens) {
|
||
let keys = 1;
|
||
let remaining = totalOpens;
|
||
|
||
while (remaining >= opensRequiredForNextKey(keys, 0)) {
|
||
remaining -= opensRequiredForNextKey(keys, 0);
|
||
keys += 1;
|
||
}
|
||
|
||
return {
|
||
keys,
|
||
opensSinceLastKey: remaining,
|
||
totalOpens,
|
||
};
|
||
}
|
||
|
||
export async function getOrCreateProgress(tx, userId, caseId) {
|
||
const existing = await tx.caseKeyProgress.findUnique({
|
||
where: { userId_caseId: { userId, caseId } },
|
||
});
|
||
if (existing) return existing;
|
||
|
||
return tx.caseKeyProgress.create({
|
||
data: { userId, caseId, keys: 1, opensSinceLastKey: 0, totalOpens: 0 },
|
||
});
|
||
}
|