Combat
The server owns every player’s health, stamina and injuries. A player’s game only reports what its swing or its arrow
hit - the game’s own hit record - and the server decides what that did, tells the mode, and tells every client what to show.
The numbers come from [combat] in server.toml (Server configuration); the game’s
own weapon and armour tables give the damage when the server has the tables export ([combat] tables).
From a hit to damage
Section titled “From a hit to damage”- The attacker’s client reports the hit: the target, the attack zone, the body part struck, and - for an arrow, a bolt or a ball - the shot it belongs to.
- The server checks it is plausible: the two are within
max_reach, the claim follows a swing the attacker announced withinclaim_window_ms, a shot was actually fired (shot_interval_ms, the ammunition in the shooter’s inventory), the two are allowed to fight (pvp, not teammates, the same virtual world). - The damage is computed. Melee: the attacker’s equipped weapon’s
Attack× the type’s modifier (stab, slash or smash) ×attack_scale, reduced by the armour the target wears on the struck part against that type (d / (d + armour_pivot), 85 % at most), × the part’s coefficient (head 1.5, torso 1, limbs 0.7), less when the swing was thrown tired, ×damage_scale. Ranged: the ammunition’s attack × the launch speed (the draw - a snap shot at half speed does a quarter) ×ranged_scale, then the same armour and part rules; every arrow is a stab. Bare hands dofist_damage. An attacker whose weapon is sheathed hits with fists whatever the claim says. Without the tables the server takes the attacker’s own number, clamped tomax_claim_damage. - The mode hears
OnPlayerDamage(pid, attacker, damage, zone, part, weapon, dtype)and may change the number, zero it or cancel the hit. - The damage comes off the health; the victim’s stamina takes
hit_stamina_damage; a hit of at leastinjury_thresholdinjures the part (OnPlayerInjury); a stab or a slash opens a bleed ofbleed_per_damage × damagehealth per second (max_bleedat most) forbleed_seconds; at0health the player dies (OnPlayerDeath) and every client shows it.
The server log names every hit: Henry#0 hit Hans#1 for 27.2 (shortswordBroad slash, raw 30.5, armour 6; zone 3, torso): health 72.8, torso injured, bleeding 0.41/s.
Stamina
Section titled “Stamina”A swing costs attack_stamina_cost (× 1.2 for a slash or a smash, × 0.5 bare-handed, × 1.25 with an injured arm); a swing
thrown with less than it costs lands at 35-100 % of its damage. A hit taken costs hit_stamina_damage, a swing blocked
block_stamina_cost, a shot shot_stamina, a sprint sprint_stamina_cost per second, a jump jump_stamina_cost. It
regenerates at stamina_regen per second after 1.5 s without any of those - halved with an injured head or torso. The bar
the player sees is this number: GetPlayerStamina / SetPlayerStamina.
Fights and the lock-on
Section titled “Fights and the lock-on”Two players are in a fight - and only then may either one’s game lock on to the other’s body (the direction indicators,
the combat stance) - once a swing of one reaches the other’s body, after /fight <name>, or when the mode says so. Without
a fight two players walking up to each other stay at peace. A fight ends after fight_timeout seconds without a blow
between the two (0 = never by time), on a death, on /peace (ends every fight of the caller), on a disconnect, or when the
mode ends it. Both players read a chat line at each change; the mode hears OnFightStart / OnFightEnd.
The mode’s side of it: StartFight (on a running fight it restarts the timeout clock - a duel mode
calls it every second so two circling duelists never lose the lock), EndFight,
AreFighting, GetPlayerOpponents,
GetPlayerWeapon.
[combat] pvp = false keeps a co-op party from locking on to or hurting each other: no fight can start and every hit
between players is refused.
SetPlayerTeam(pid, team) puts players in teams; a hit between two players
of one team is refused before OnPlayerDamage and starts no fight. NO_TEAM (-1) is no team. A mode that wants
friendly fire on writes its rule in OnPlayerDamage instead of using teams.
Ranged weapons
Section titled “Ranged weapons”A bow, a crossbow or a gun works through the server like a sword: the shooter’s client reports the shot as it leaves the
weapon (the ammunition, the speed - which is the draw), the server admits it (a bow among the equipment, the ammunition in
the inventory, shot_interval_ms between shots, shot_stamina), every client in range flies a look-alike, and the hit is
the shooter’s claim within the flight time plus shot_window_seconds. OnPlayerDamage names the weapon as
"bow_c + arrow_normal" and the type as "stab". The archer’s strength limits the draw as in the game
(SetPlayerStat).
NPC actors
Section titled “NPC actors”A player’s hit on an NPC actor goes through the same model (no armour on the actor) and comes to the mode as
OnActorDamage; an actor’s blow on a player is priced by the victim’s own game (the actor’s weapon through their armour)
× [actors] attack_scale and comes as OnActorAttack. An armed actor raises its guard against a swing part of the time ([actors] block_chance,
block_hold) and swings where the opponent’s guard is not; its wounds and bleeding follow the same [combat] keys as a player’s
(OnActorInjury, HealActor).
Deciding a hit
Section titled “Deciding a hit”Everything the mode needs is in the callback: cancel, scale, redirect, log.
-- no damage in the lobby zone, double damage to the head, a log line for every killfunction OnPlayerDamage(pid, attacker, damage, zone, part, weapon, dtype) if IsPlayerInZone(pid, lobby) then return false end if part == BODY_PART_HEAD then return damage * 2 endend
function OnPlayerDeath(pid, attacker) if attacker >= 0 then Log(GetPlayerName(attacker) .. " killed " .. GetPlayerName(pid) .. " with " .. GetPlayerWeapon(attacker)) endendHealPlayer(pid) makes a player whole; SetPlayerHealth(pid, 0) kills one; [combat] respawn_seconds = 0 leaves the
respawn to the mode’s SpawnPlayer.
