This commit is contained in:
parent
cb2d1a099f
commit
30531bcd52
|
|
@ -0,0 +1,69 @@
|
||||||
|
import { component } from "../core/registry";
|
||||||
|
import { Component } from "../core/world";
|
||||||
|
import { action, 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 0–1 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue