step 10.3: done

This commit is contained in:
bepis
2026-10-22 22:44:49 +11:00
parent 37e2219d32
commit 441efb06bd
5 changed files with 124 additions and 27 deletions
+1 -1
View File
@@ -48,7 +48,7 @@ async function handleClaim(req, res) {
const { expeditionId } = parsed.data;
const characterId = getCharacterIdForUser(req.session.userId);
const result = claimExpedition(characterId, expeditionId);
const result = claimExpedition(characterId, expeditionId, req.body._opts);
res.json({ ok: true, data: result });
}
+15 -10
View File
@@ -4,7 +4,7 @@ const { getDb } = require('../db');
const { AppError } = require('../utils/errors');
const { computeEnergy } = require('../game/energy');
const { applyXpGain } = require('../game/leveling');
const { now, setNow } = require('../utils/time');
const time = require('../utils/time');
// Prepared statements cached per DB handle (WeakMap).
const stmtCache = new WeakMap();
@@ -93,9 +93,11 @@ function getStmts(db) {
* Start an expedition for a character.
* @param {number} characterId
* @param {number} defId
* @param {{now?: number}} [opts] - injectable now for tests
* @returns {object} { expedition, definition }
*/
function startExpedition(characterId, defId) {
function startExpedition(characterId, defId, opts = {}) {
const now = opts.now ?? time.now();
const db = getDb();
const s = getStmts(db);
@@ -117,7 +119,7 @@ function startExpedition(characterId, defId) {
// Lazy energy evaluation.
const { energy, updatedAtMs } = computeEnergy(
ch.energy, ch.energy_updated_at, now(),
ch.energy, ch.energy_updated_at, now,
{ max: 100, regenRate: 1, regenInterval: 180 }
);
@@ -126,7 +128,7 @@ function startExpedition(characterId, defId) {
}
const newEnergy = energy - def.energy_cost;
const startedAt = now();
const startedAt = now;
const endsAt = startedAt + (def.duration_sec * 1000);
// Execute inside a transaction.
@@ -168,16 +170,17 @@ function startExpedition(characterId, defId) {
/**
* Get the active (unclaimed) expedition for a character.
* @param {number} characterId
* @param {{now?: number}} [opts] - injectable now for tests
* @returns {object|null} { expedition, definition, timeRemaining } or null
*/
function getActive(characterId) {
function getActive(characterId, opts = {}) {
const db = getDb();
const s = getStmts(db);
const row = s.getActive.get(characterId);
if (!row) return null;
const nowMs = now();
const nowMs = opts.now ?? time.now();
const timeRemaining = Math.max(0, row.ends_at - nowMs);
return {
@@ -207,9 +210,10 @@ function getActive(characterId) {
* Claim a finished expedition and grant rewards.
* @param {number} characterId
* @param {number} expeditionId
* @param {{rng?: function, now?: number}} [opts] - injectable rng/now for tests
* @returns {object} { xpGained, goldGained, expedition }
*/
function claimExpedition(characterId, expeditionId) {
function claimExpedition(characterId, expeditionId, opts = {}) {
const db = getDb();
const s = getStmts(db);
@@ -224,7 +228,7 @@ function claimExpedition(characterId, expeditionId) {
throw new AppError('ALREADY_CLAIMED', 'This expedition has already been claimed.', 409);
}
const nowMs = now();
const nowMs = opts.now ?? time.now();
if (nowMs < exp.ends_at) {
throw new AppError('NOT_FINISHED', 'Expedition has not finished yet.', 409);
}
@@ -237,9 +241,10 @@ function claimExpedition(characterId, expeditionId) {
const ch = s.character.get(characterId);
if (!ch) throw new AppError('NOT_FOUND', 'Character not found.', 404);
// Calculate rewards.
// Calculate rewards using injectable rng.
const xpGained = def.xp_reward;
const goldGained = def.gold_min + Math.floor(Math.random() * (def.gold_max - def.gold_min + 1));
const rng = opts.rng || (() => Math.random());
const goldGained = def.gold_min + Math.floor(rng() * (def.gold_max - def.gold_min + 1));
const newGold = ch.gold + goldGained;
// Apply XP gain (adds to current, handles level-up, accumulates stat points).