miaoda-game-stats-core
v0.2.0
Published
Engine-agnostic character-stat + modifier/buff-stack engine — HP, attack, move-speed and the like, computed from a base value plus stacked modifiers with a correct, fixed resolution order (flat add → additive-percent → multiplicative-percent), source grou
Maintainers
Readme
miaoda-game-stats-core
Use this package for derived character attributes such as attack, armor, move speed, or maximum HP. A stat combines a base value with flat, additive-percent, and multiplicative-percent modifiers, optional bounds, source tags, and timed buffs. It does not own current HP, damage rules, rendering, or a clock.
Install
pnpm add miaoda-game-stats-coreDefine stats and modifiers
import { Stat, StatSet } from 'miaoda-game-stats-core';
const attack = new Stat(10);
attack.addModifier('flat', 5, { source: 'sword' });
const rage = attack.addModifier('percentAdd', 0.5);
attack.value; // 22.5
attack.removeModifier(rage);
const hero = new StatSet({
maxHp: 100,
attack: { base: 10, bounds: { min: 0 } },
moveSpeed: 5,
});
hero.applyBuff({
id: 'slow',
duration: 3,
modifiers: [{ stat: 'moveSpeed', op: 'percentAdd', value: -0.5 }],
});
hero.tick(3); // timed values use secondsModifier resolution is order-independent:
(base + sum(flat)) * (1 + sum(percentAdd)) * product(1 + percentMult)percentAdd values are fractions (0.5 means +50%) and are summed before applying. percentMult values compound individually. Bounds clamp the final result.
Buffs and time
applyBuff replaces an existing buff with the same ID, refreshing its duration instead of duplicating it. A buff can target several stats; unknown target stats are skipped. tick(dtSeconds) returns the number of expired modifiers and must be called by your game loop or an engine adapter.
hero.listBuffs();
hero.hasBuff('slow');
hero.removeBySource('sword');
hero.clearBuffs();Use source tags for equipment, auras, or a whole effect family that should be removed together. get, setBase, modifier operations, and onChange throw for an undefined stat; call has or stat first when names are optional.
Observe and save
const stop = hero.onChange('attack', (value) => renderAttack(value));
const save = hero.toJSON();
hero.loadJSON(save);toJSON includes bases, bounds, live modifiers, and remaining durations. Buff registry links are intentionally flattened when saved, so loaded modifiers still expire and can be removed by source but are no longer removable by their original buff ID. Loading is atomic and validates finite values and duplicate IDs.
Public API
| API | Use it for |
| --- | --- |
| computeModifiers / clamp | Pure stat math |
| Stat | One derived value and its modifiers |
| StatSet | A named sheet with buffs, listeners, ticking, and JSON save data |
| addModifier / removeModifier | Equipment and one-off effects |
| applyBuff / removeBuff | Timed or grouped multi-stat effects |
| onChange | Update HUDs and derived gameplay values |
Pair this package with a resource or health system for current pools, status-core for ongoing effects, and combat2d-core for accepted hits. The stat engine supplies values; it does not apply damage or regenerate resources.
