Spaces:
Running
Running
File size: 3,278 Bytes
d0eb71c f3c5314 d0eb71c f92b745 f3c5314 f92b745 d0eb71c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | // Pure progression math + event helpers — the single source of truth for the XP curve
// so no UI re-derives it. See docs/rpg-progression-plan.md (Phase 0/1).
//
// Curve: a gentle quadratic so early levels come fast and later ones slow down.
// xpToNext(L) = BASE + GROWTH * (L-1)^1.5, rounded to a tidy step.
const BASE = 50
const GROWTH = 40
const ATTR_PER_LEVEL = 0 // level-up reward is now the roguelike perk draft (levelUpCards.js), not points
export function xpToNext(level) {
const l = Math.max(1, level | 0)
return Math.round((BASE + GROWTH * Math.pow(l - 1, 1.5)) / 10) * 10
}
// Total XP needed to have *reached* a given level from level 1 (handy for bars/among UIs).
export function xpForLevel(level) {
let sum = 0
for (let l = 1; l < Math.max(1, level | 0); l++) sum += xpToNext(l)
return sum
}
// Apply `amount` XP to a progression object, rolling over as many levels as the XP covers.
// Returns a NEW progression object plus what happened, so callers can react (toast, points).
export function applyXp(progression, amount) {
const p = { level: 1, xp: 0, kills: 0, attributePoints: 0, ...(progression || {}) }
let { level, xp, attributePoints } = p
xp += Math.max(0, amount | 0)
let leveledUp = false, levelsGained = 0
let need = xpToNext(level)
while (xp >= need) {
xp -= need
level += 1
levelsGained += 1
attributePoints += ATTR_PER_LEVEL
leveledUp = true
need = xpToNext(level)
}
return {
progression: { ...p, level, xp, attributePoints },
leveledUp,
levelsGained,
xpToNext: need,
}
}
// Fraction [0,1] toward the next level — for the XP bar.
export function levelProgress(progression) {
const p = progression || {}
const need = xpToNext(p.level || 1)
return need > 0 ? Math.min(1, Math.max(0, (p.xp || 0) / need)) : 0
}
// XP a defeated foe is worth. Small table keyed by a coarse tier/class string; unknown
// enemies fall back to a sane default. Tunable in one place.
const ENEMY_XP = { weak: 8, normal: 12, tough: 20, elite: 40, boss: 100 }
export function enemyXpValue(enemy) {
if (!enemy) return ENEMY_XP.normal
if (typeof enemy.xp === 'number') return enemy.xp
const tier = (enemy.tier || enemy.rank || '').toLowerCase()
if (ENEMY_XP[tier]) return ENEMY_XP[tier]
return ENEMY_XP.normal
}
// Combat stat scaling from level + spent attribute points. rank feeds the engine's skill
// scaling; hpMul/dmgMul scale survivability + damage so leveling actually makes you stronger.
export function statBonuses(progression) {
const p = progression || {}
const lvl = p.level || 1
const spent = p.spent || { hp: 0, dmg: 0 }
return {
rank: Math.min(20, 7 + lvl),
hpMul: 1 + (lvl - 1) * 0.07 + (spent.hp || 0) * 0.05,
dmgMul: 1 + (lvl - 1) * 0.05 + (spent.dmg || 0) * 0.05,
speedMul: 1 + (spent.spd || 0) * 0.06,
}
}
// Append a capped, structured event to a persona's log (newest last). Used for the chat
// "what's happening to you" context (woid-style perception log) and the level-up feed.
const MAX_EVENTS = 60
export function appendEvent(events, type, data = {}) {
const next = Array.isArray(events) ? events.slice() : []
next.push({ type, ...data })
return next.length > MAX_EVENTS ? next.slice(next.length - MAX_EVENTS) : next
}
|