1
0
Fork 0
tsgames/src/common/rpg/components/experience.ts

69 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Component } from "../core/world";
import { action, component, variable } from "../utils/decorators";
/**
* How much XP is required to advance from level N to N+1.
*
* - `number[]` — explicit table; `values[0]` = XP for level 1→2.
* Max level = `values.length + 1`. Past the end the entity is at max level.
* - `{ base, factor }` — geometric: XP[N] = floor(base * factor^(N-1)).
* No max level.
*/
export type ThresholdSpec = number[] | { base: number; factor: number };
function xpForStep(spec: ThresholdSpec, level: number): number | null {
if (Array.isArray(spec)) {
const idx = level - 1;
return idx < spec.length ? spec[idx] : null;
}
return Math.floor(spec.base * Math.pow(spec.factor, level - 1));
}
@component
export class Experience extends Component<{
xp: number;
level: number;
xpAtLevel: number; // total XP accumulated when current level was reached
spec: ThresholdSpec;
}> {
constructor(spec: ThresholdSpec) {
super({ xp: 0, level: 1, xpAtLevel: 0, spec });
}
/** Current level, starting at 1. Primary variable for conditions: `experience >= 5`. */
@variable get level(): number { return this.state.level; }
/** Total accumulated XP (never resets). */
@variable('.') get xp(): number { return this.state.xp; }
/** XP accumulated within the current level. */
@variable get xpInLevel(): number { return this.state.xp - this.state.xpAtLevel; }
/** XP remaining until the next level, or `null` at max level. */
get xpToNext(): number | null {
const needed = xpForStep(this.state.spec, this.state.level);
return needed === null ? null : needed - this.xpInLevel;
}
/** Progress toward the next level as a 01 fraction. `1` at max level. */
@variable get progress(): number {
const needed = xpForStep(this.state.spec, this.state.level);
if (needed === null) return 1;
return Math.min(this.xpInLevel / needed, 1);
}
/** Add XP and emit `'levelup'` with `{ level, prev }` for each level gained. */
@action
award(xp: number): void {
this.state.xp += xp;
while (true) {
const needed = xpForStep(this.state.spec, this.state.level);
if (needed === null) break;
if (this.xpInLevel < needed) break;
this.state.xpAtLevel += needed;
const prev = this.state.level++;
this.emit('levelup', { level: this.state.level, prev });
}
}
}