Levels and skills
Experience, a level curve, attribute and skill points, skill trees as data, and the summed stats other kits read.
Levels, attributes and skills (progression.levels) #
What it is for #
progression.levels gives a game experience points, levels, points to spend on attributes and on skills, and the stats they grant. XP comes from the kills made by the player, from missions and from your own rules. A level is bought with a growing curve; each level grants attribute points and skill points. Skills are plain data (the skills collection) with trees, costs, minimum level and prerequisites. Everything unlocked is summed into stats that combat.core applies: maximum health, damage and armour. Each player entity has its own sheet, so co-op works; the HUD variables show the sheet of the HUD player. The kit shows the level badge and the character sheet through an ui.webui fragment.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "progression.levels" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "progression.levels",
config: { "maxLevel": 30, "baseXp": 120, "xpByTag": { "boss": 500, "elite": 80 } } }
It requires game.engine. Kills only count when combat.core is installed (the kit listens to combat.killed); the stats reach the player's health and damage through combat.health and combat.damage. The key C opens the character sheet (input action character_sheet).
Skills are a data collection of the project:
{ "skills": [
{ "id": "cut", "name": "Cut", "tree": "one_handed", "cost": 1, "stats": { "damage_add": 2 } },
{ "id": "deep", "name": "Deep cut", "tree": "one_handed", "requires": ["cut"], "minLevel": 3, "stats": { "damage_add": 3 } }
] }
First example #
<!-- test: kits/game_engine/host/tests/progression_levels.rs::xp_gives_levels_points_and_stats --> The test sets maxLevel: 3, baseXp: 100, growth: 2 and the two skills above, then drives the service progression.levels:
toNext()answers100;addXp(120.5)crosses level 2 and answers the XP kept (20.5).points()is{ attribute: 1, skill: 1 }.unlock("deep")isfalse(level and prerequisite),unlock("cut")istrue.spendAttribute("health")istrueonce, thenfalse(no point left).stats()is{ damage_add: 2, health_max: 10 }.- A big
addXp(500)stops at the cap:level()is3,toNext()is0, andunlock("deep")now works.
Settings and defaults #
| Setting | Type | Default | Meaning |
|---|---|---|---|
maxLevel | integer (2..999) | 50 | level cap; XP stops there |
baseXp | number | 100 | XP to reach level 2 |
growth | number (1..3) | 1.18 | each level needs this times the previous cost |
xpPerKill | number | 20 | XP for a player kill when the victim has no xp component (0: none) |
xpByTag | object | {} | XP by victim tag, e.g. { "boss": 500 }; the highest matching tag wins over xpPerKill |
attributePointsPerLevel | integer (0..20) | 1 | attribute points granted per level |
skillPointsPerLevel | integer (0..20) | 1 | skill points granted per level |
attributes | list | health, stamina, magicka | what an attribute point buys: { id, label, stat, perPoint } |
healOnLevelUp | boolean | true | a level up restores the player to full health |
The default attributes raise the stats health_max, stamina_max and mana_max by 10 per point. The cost of going from level L to L + 1 is baseXp x growth^(L - 1), rounded. A victim's own xp component, if it has one, is its worth.
A skill of the skills collection has id, name, and optionally tree, description, cost (skill points, default 1), requires (skills to unlock first), minLevel and stats (numbers granted while unlocked). All settings and the skills collection are moddable.
Services, events, variables #
The service progression.levels (about the HUD player or the first sheet):
xp(),level(),toNext(): current XP, level, and XP missing for the next level (0at the cap).addXp(amount, reason?): adds XP, answers the XP kept.points():{ attribute, skill }points available.attributes(): the attributes with their currentrank.spendAttribute(id): spends one point;falseif none left, unknown attribute, or a hook refused.skills(): the skills withunlockedandavailable;unlocked(id),canUnlock(id),unlock(id).stat(name)andstats(): summed stats. Stats understood by other kits:health_max(throughcombat.health),damage_addanddamage_mult(an outgoing modifier ofcombat.damage),armor(an incoming reduction of100 / (100 + armor)). Any other name is kept for your own use.sheet(open?): opens or closes the character sheet and answers its state.
Events: progression.xp (amount, xp, level, reason), progression.levelup (level, attributePoints, skillPoints), progression.attribute (attribute, rank, stat, value), progression.skill (skill, tree), progression.stats (stats). The kit listens to combat.killed, mission.completed (XP from rewards.xp) and game.phase (a pause closes the sheet).
Variables: progression.sheet, progression.level, progression.xp, progression.xpNext, progression.attributePoints, progression.skillPoints.
Verbs for rules and scripts: add_xp (action, amount, optional reason), unlock_skill (action, skill), and the conditions level_at_least (level) and skill_unlocked (skill):
{ "add_xp": 50 }
{ "unlock_skill": "cut" }
{ "level_at_least": 5 }
Hooks and modding #
xp.gain:{ player, amount, reason, level };amountis mutable; cancellable. Cancelled or 0: no XP, no event.level.up:{ player, level, attributePoints, skillPoints }; both point counts are mutable; cancellable (the level is not gained, the XP is kept and the level is asked again at the next gain).attribute.spend:{ player, attribute, stat, rank, cost };costis mutable; cancellable.skill.unlock:{ player, skill, tree, cost };costis mutable; cancellable.
A mod can double XP in a zone, grant extra points at level 10, or refuse a skill while a quest is open, without touching the kit. The settings and the skills collection can also be changed by a mod, and an entry can extend another.
Engines #
The logic is one Rust module, identical on the web, the native player, Godot, Unity and Unreal. The level badge and the character sheet are an ui.webui fragment.
When it does not do what you expect #
- No XP from kills: the killer must be the player and
combat.killedmust be emitted (installcombat.core);xpPerKillmay be0; or anxp.gaintap cancelled it. unlock(id)answersfalse: not enough skill points, level belowminLevel, a prerequisite missing, already unlocked, or a hook refused.canUnlock(id)tells before trying.- Maximum health does not change: the stat is
health_maxand needscombat.health. - XP stays at 0 at the top: the cap was reached.
Limits #
- No respec: points are not given back.
- Stats other than
health_max,damage_add,damage_multandarmorare only summed; your game reads them withstat. - The damage modifiers follow the sheet of the HUD player.
Versions #
Kit version 1.2.0 (service contract 1.1.0). 1.1.0 added one sheet per player entity (co-op); 1.2.0 added the hooks and the moddable settings.