step 10.12: done

This commit is contained in:
bepis
2026-10-23 04:33:19 +11:00
parent 08d880ab73
commit 4277b9c273
8 changed files with 286 additions and 51 deletions
+42
View File
@@ -0,0 +1,42 @@
// Equipment bonuses: sum of bonus_* fields over all equipped items for a character.
// Single shared implementation used by characterService, huntService, inventoryService, shopService.
const { getDb } = require('../db');
/**
* Sum equipment bonuses for a character.
* @param {number} characterId
* @returns {{ bonus_str: number, bonus_agi: number, bonus_int: number, bonus_vit: number, bonus_attack: number, bonus_defense: number }}
*/
function getEquipmentBonuses(characterId) {
const db = getDb();
const rows = db.prepare(
`SELECT it.bonus_str, it.bonus_agi, it.bonus_int, it.bonus_vit,
it.bonus_attack, it.bonus_defense
FROM inventory i
JOIN items it ON it.id = i.item_id
WHERE i.character_id = ? AND i.equipped_slot IS NOT NULL`
).all(characterId);
const bonuses = {
bonus_str: 0,
bonus_agi: 0,
bonus_int: 0,
bonus_vit: 0,
bonus_attack: 0,
bonus_defense: 0,
};
for (const row of rows) {
bonuses.bonus_str += row.bonus_str || 0;
bonuses.bonus_agi += row.bonus_agi || 0;
bonuses.bonus_int += row.bonus_int || 0;
bonuses.bonus_vit += row.bonus_vit || 0;
bonuses.bonus_attack += row.bonus_attack || 0;
bonuses.bonus_defense += row.bonus_defense || 0;
}
return bonuses;
}
module.exports = { getEquipmentBonuses };
+11 -5
View File
@@ -6,6 +6,7 @@ const { getBaseStats } = require('../game/classes');
const { calcDerived, computeEnergy, ENERGY_MAX, ENERGY_REGEN_SECONDS } = require('../game/stats');
const { computeEnergy: computeEnergyPure } = require('../game/energy');
const { computeHp } = require('../game/hp');
const { getEquipmentBonuses } = require('../game/equipment');
function wrapStmts(db) {
@@ -78,11 +79,13 @@ function getCharacterProfile(userId) {
return null;
}
// Compute derived stats (with no equipment bonuses for now)
// Compute derived stats including equipment bonuses
const equipBonuses = getEquipmentBonuses(character.id);
const derived = calcDerived(
{ str: character.str, agi: character.agi, int: character.int, vit: character.vit },
character.level,
character.class
character.class,
equipBonuses
);
// Compute current energy with lazy regen
@@ -117,7 +120,8 @@ function getCharacterProfile(userId) {
derived.maxHp = calcDerived(
{ str: character.str, agi: character.agi, int: character.int, vit: character.vit },
character.level,
character.class
character.class,
equipBonuses
).maxHp;
}
@@ -321,11 +325,13 @@ function rest(userId) {
throw new AppError('NO_CHARACTER', 'No character found.', 404);
}
// Compute derived stats to know max HP
// Compute derived stats including equipment bonuses to know max HP
const equipBonuses = getEquipmentBonuses(character.id);
const derived = calcDerived(
{ str: character.str, agi: character.agi, int: character.int, vit: character.vit },
character.level,
character.class
character.class,
equipBonuses
);
const cost = character.level * 5;
+6 -4
View File
@@ -11,6 +11,7 @@ const { resolveCombat } = require('../game/combat');
const { calculateRewards, rollLoot } = require('../game/rewards');
const { checkLevelUp, applyXpGain } = require('../game/leveling');
const { computeHp } = require('../game/hp');
const { getEquipmentBonuses } = require('../game/equipment');
const MIN_HP_PCT_TO_HUNT = 0.2;
const DEFEAT_GOLD_LOSS_PCT = 0.10;
@@ -94,7 +95,8 @@ function hunt(characterId, zoneId, opts = {}) {
}
const baseStats = { str: ch.str, agi: ch.agi, int: ch.int, vit: ch.vit };
const derived = calcDerived(baseStats, ch.level, ch.class);
const equipBonuses = getEquipmentBonuses(characterId);
const derived = calcDerived(baseStats, ch.level, ch.class, equipBonuses);
// Apply lazy HP regen before checking HP threshold
const hpUpdatedAt = ch.hp_updated_at ?? ch.energy_updated_at;
@@ -116,7 +118,7 @@ function hunt(characterId, zoneId, opts = {}) {
if (monsters.length === 0) throw new AppError('ZONE_EMPTY', 'No monsters in this zone.', 500);
const monster = pickWeightedMonster(monsters, rng);
const fight = resolveCombat({ ...baseStats, class: ch.class, level: ch.level, hpCurrent: ch.hp_current }, monster, rng);
const fight = resolveCombat({ ...baseStats, class: ch.class, level: ch.level, hpCurrent: ch.hp_current, ...equipBonuses }, monster, rng);
let { xp, gold, level, unspent_points: unspent, kills, deaths } = ch;
let hp = fight.hpRemaining;
@@ -146,7 +148,7 @@ function hunt(characterId, zoneId, opts = {}) {
leveledUp = true;
level = xpResult.level;
unspent += xpResult.unspentPoints - ch.unspent_points;
hp = calcDerived(baseStats, level, ch.class).maxHp; // full heal at the NEW max HP
hp = calcDerived(baseStats, level, ch.class, equipBonuses).maxHp; // full heal at the NEW max HP
}
} else {
goldLost = Math.floor(ch.gold * DEFEAT_GOLD_LOSS_PCT);
@@ -162,7 +164,7 @@ function hunt(characterId, zoneId, opts = {}) {
winner: fight.winner,
rounds: fight.rounds,
hpRemaining: hp,
hpMax: calcDerived(baseStats, level, ch.class).maxHp,
hpMax: calcDerived(baseStats, level, ch.class, equipBonuses).maxHp,
xpGained, goldGained, goldLost, loot, leveledUp,
newLevel: level,
energy: energy.energy - zone.energy_cost,
+3 -1
View File
@@ -4,6 +4,7 @@ const { getDb } = require('../db');
const { AppError } = require('../utils/errors');
const { calcDerived, ENERGY_MAX } = require('../game/stats');
const { computeHp } = require('../game/hp');
const { getEquipmentBonuses } = require('../game/equipment');
const time = require('../utils/time');
const VALID_SLOTS = new Set(['weapon', 'armor', 'trinket']);
@@ -280,9 +281,10 @@ function useConsumable(characterId, itemId) {
const applyEffect = db.transaction((rowId, qty, etype, evalue, charId, c, hat) => {
// Apply the effect.
if (etype === 'heal') {
const equipBonuses = getEquipmentBonuses(charId);
const maxHp = calcDerived(
{ str: c.str, agi: c.agi, int: c.int, vit: c.vit },
c.level, c.class
c.level, c.class, equipBonuses
).maxHp;
// Apply lazy HP regen before healing
const hpUpdatedAt = c.hp_updated_at ?? c.energy_updated_at;
+3 -16
View File
@@ -2,6 +2,8 @@
// Sell value = 40% of buy_price (materials with buy_price=0 use rarity default).
const { getDb } = require('../db');
const { AppError } = require('../utils/errors');
const { getEquipmentBonuses } = require('../game/equipment');
const { calcDerived } = require('../game/stats');
// Rarity defaults for materials (buy_price = 0).
const MATERIAL_SELL_DEFAULTS = { common: 2, uncommon: 4, rare: 8, epic: 16, legendary: 32 };
@@ -278,22 +280,7 @@ function listInventory(characterId) {
const rows = s.listInventory.all(characterId);
// Calculate current derived stats including equipment bonuses.
const equipBonuses = {
bonus_str: 0, bonus_agi: 0, bonus_int: 0, bonus_vit: 0,
bonus_attack: 0, bonus_defense: 0,
};
for (const row of rows) {
if (row.equipped_slot) {
equipBonuses.bonus_str += row.bonus_str || 0;
equipBonuses.bonus_agi += row.bonus_agi || 0;
equipBonuses.bonus_int += row.bonus_int || 0;
equipBonuses.bonus_vit += row.bonus_vit || 0;
equipBonuses.bonus_attack += row.bonus_attack || 0;
equipBonuses.bonus_defense += row.bonus_defense || 0;
}
}
const { calcDerived } = require('../game/stats');
const equipBonuses = getEquipmentBonuses(characterId);
const derived = calcDerived(
{ str: ch.str, agi: ch.agi, int: ch.int, vit: ch.vit },
ch.level,