dot-player-class describes what a player is — health, armour, movement
multipliers, ability and loadout ids, team restrictions and limits — as a
document a dedicated server can validate without loading a mesh, an animation
set or a weapon.
Requires dot-core and dot-player.
Usage
var classes := DotPlayerClassManager.new()
classes.catalogue = DotPlayerClassCatalogue.team_shooter()
classes.rules = DotPlayerClassRules.standard()
classes.team_fn = func(key): return teams.team_of(key)
classes.alive_fn = func(key): return roster.get_record(key).alive
add_child(classes)
classes.setup()
var res := classes.request("ada", &"sniper")
if not res.ok:
hud.say(res.error.message) # "Your side already has 2 Sniper."
A change lands on the next spawn
Changing class while somebody is shooting at you changes your health, your speed and your weapons mid-fight. A request is recorded instead, and the next spawn honours it:
classes.request("ada", &"medic") # accepted, but not yet
classes.pending_of("ada") # &"medic"
classes.class_of("ada") # still &"assault"
var spawning_as := classes.apply_pending("ada") # on spawn: &"medic"
A player who is already dead gets the change immediately — there is nothing to exploit, and making them wait for a second death is confusing.
What is in it
DotPlayerClassDef |
One class: health, armour and its absorption, movement multipliers, mass, content ids, abilities, team restrictions, limits, entitlement. |
DotPlayerClassCatalogue |
The set. Three presets: single, team_shooter (six classes tuned as relations) and asymmetric. |
DotPlayerClassRules |
When a choice may be made and when it lands. Four presets, and a DotConfig. |
DotPlayerClassManager |
Current class, pending class, limits, entitlements, and a mirror for clients. |
single() exists so a game with no classes still has one: modelling it as no
catalogue makes every consumer branch on whether classes exist.
Everything is a number or an id
A class names its loadout, model, animation set and abilities; it does not contain them. Resolution happens on a client, through a catalogue, through dot-cloud. The addon’s self-test loads no content, which is the check on that promise.
Refusals say why
Every entry point returns a DotResult, because each refusal has a sentence a
player needs to read.
limit_per_team |
“Your side already has 2 Sniper.” |
teams |
“Breacher is not available to your side.” |
requires_entitlement |
“You do not have Heavy.” The check itself is the game’s — an addon that decided entitlement would be one a client could patch. |
allow_midround_change |
“Classes are locked once the round has started.” |
options_for(key) returns every class with available, why, taken and
limit — the difference between a greyed-out button and a greyed-out button
with a tooltip.
The armour arithmetic
var hit := def.absorb(50.0, current_armour)
health -= hit["health_damage"]
armour = hit["armour_left"]
It lives here rather than in dot-combat because the numbers that decide it are here, and a game doing the arithmetic itself is a game where the class screen and the damage model disagree about what 150 armour means.
