feat: complete Phase 4 combat/hunting system

- Add content migration (003_content.sql): zones, monsters, items, loot tables
- Add inventory migration (004_inventory.sql) and logs migration (006_logs.sql)
- Add Mulberry32 PRNG (src/utils/rng.js) for deterministic combat
- Add combat system (src/game/combat.js) with turn order, crit, mage bonus, 20-round cap
- Add rewards/loot system (src/game/rewards.js) and leveling (src/game/leveling.js)
- Add hunt endpoint (POST /api/hunt) with full combat flow in DB transaction
- Add hunt integration tests and unit tests for rewards/leveling/combat
- Fix session middleware to use memory store in tests (avoids cleanup hang)
- Fix huntService transaction (db.transaction() returns wrapper, must call separately)
- Add huntService to helpers.js cache invalidation for test isolation
This commit is contained in:
bepis
2026-10-20 17:46:18 +11:00
parent 7a3e408ffc
commit f1ae7b3119
16 changed files with 1239 additions and 21 deletions
+44
View File
@@ -0,0 +1,44 @@
// Hunt routes: POST /api/hunt (fight a monster in a zone).
const zod = require('zod');
const { AppError } = require('../utils/errors');
const { requireAuth } = require('../middleware/session');
const { hunt } = require('../services/huntService');
// Validation schema
const huntSchema = zod.strictObject({
zoneId: zod.number().int().positive(),
});
function wrap(fn) {
return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
}
async function handleHunt(req, res) {
const parsed = huntSchema.safeParse(req.body);
if (!parsed.success) {
throw new AppError('VALIDATION_ERROR', 'Request validation failed.', 400);
}
const { zoneId } = parsed.data;
const userId = req.session.userId;
// Get character ID for this user
const db = require('../db').getDb();
const character = db.prepare('SELECT id FROM characters WHERE user_id = ?').get(userId);
if (!character) {
throw new AppError('NO_CHARACTER', 'No character found.', 404);
}
const result = hunt(character.id, zoneId);
res.json({
ok: true,
data: result,
});
}
module.exports = {
routes: {
postHunt: wrap(handleHunt),
},
};