Files
transcience/src/net/protocol.gd
T
claudeandClaude Opus 5 a6084c1a8d
ci / verify (push) Successful in 52s
Tell the roster the truth about a wounded character
Health survives a run now, so a character sitting on the select screen
can be nearly dead -- and the screen was still printing only the ceiling.
"level 11, 2000 hp" for a character on 160 is a lie told at the exact
moment it matters most, because the next thing that happens is the player
walks it into a dungeon under permadeath.

The roster message carries current hit points alongside the maximum, and
the screen shows a fraction only when it means something: a healthy
character still reads "1300 hp" rather than "1300/1300". The field is
inserted mid-record, which shifts every field after it, so the protocol
version goes to 10 -- a mismatched client would read the active flag out
of the middle of a health value.

tools/screenshot.gd had no capture for this screen, which is how a UI
change to it could have shipped unlooked-at. It now shows a made-up
roster of three characters -- whole, badly hurt, nearly dead -- for the
same reason the upgrade screen is fed a made-up offer: the point is the
layout, not a dungeon run. Getting there needs the player walked off the
quartermaster first, or the game scene re-opens the upgrade screen over
the top of it every frame.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 21:54:40 +02:00

75 lines
3.5 KiB
GDScript

class_name Protocol
extends RefCounted
## Wire constants. Bump [constant VERSION] whenever a codec layout changes; the
## server refuses mismatched clients at handshake rather than desyncing later.
## 2: added spawn-grace flag + cleared countdown to the snapshot, and the
## roster message.
## 3: dropped the post-hit invulnerability flag, added the respawn-lockout byte
## to each player record.
## 4: added SimEvent.Type.PLAYER_FIRED. It was inserted mid-enum, which shifts
## the wire value of every event after it -- a mismatched client would
## mis-decode every hit and death, so the handshake has to reject it.
## 5: handshake carries an auth ticket instead of a bare name; added character
## list/select/create messages, per-player max health and colour in the
## snapshot.
## 6: inventory and loot. The snapshot gained the observer's own inventory and
## the ground-loot list, the input frame gained a slot byte, and three item
## events were appended. Every one of those changes the byte layout of a
## message both ends parse positionally.
## 7: more than one dungeon. enter_instance carries a portal LIST and the id of
## the dungeon you are standing in, replacing the single portal position.
## 8: upgrades. A new server -> client upgrade-state message, a new
## client -> server choice message, and two more SelectResult values.
## 9: telegraphed boss attacks. A new TELEGRAPH event, appended to the enum.
## 10: health survives a run, so the character roster carries current hit
## points as well as the ceiling. Inserted mid-record, which shifts every
## field after it -- a mismatched client would read the active flag out of
## the middle of a health value.
const VERSION := 10
const DEFAULT_PORT := 27015
const MAX_CLIENTS := 32
## ENet channels. Separating them stops a burst of reliable bullet events from
## head-of-line blocking the unreliable snapshot stream.
const CH_CONTROL := 1 ## handshake, instance transitions -- reliable
const CH_SNAPSHOT := 2 ## world state -- unreliable, newest wins
const CH_EVENTS := 3 ## bullet spawns/despawns, hits -- reliable ordered
const CH_INPUT := 4 ## client -> server intent -- unreliable ordered
## Must be identical on both ends, and larger than the highest channel above.
const CHANNEL_COUNT := 8
enum InstanceKind { LOBBY, DUNGEON }
## Why a character selection failed. Sent rather than a bare "no", so the UI can
## say something useful instead of appearing broken.
enum SelectResult {
OK,
NO_SUCH_CHARACTER,
CHARACTER_IS_DEAD,
LIMIT_REACHED,
NOT_AUTHENTICATED,
## Swapping is hub-only. Allowing it inside a dungeon would be an instant,
## uninterruptible exit from danger -- strictly better than the one-second
## escape channel, and it would make that channel pointless.
NOT_IN_HUB,
## Upgrades are spent standing at the hub's NPC. Same rule as the portal:
## where you are is the one thing a modified client cannot fake.
NOT_AT_THE_NPC,
## No unspent level-up, or an option index that was not on the table.
NOTHING_TO_CHOOSE,
}
## Player flags packed into the snapshot's per-player byte.
const F_ALIVE := 1
const F_ESCAPING := 4
## Arrival protection: invulnerable and unable to shoot. The only invulnerable
## state there is -- see the i-frames note in SimConfig.
const F_SPAWN_GRACE := 8
## The peer behind this player has dropped and is being channelled out.
const F_LINKDEAD := 16
## Snapshot's cleared-countdown byte when the instance is not in the cleared
## state -- 30s fits in a byte, so 255 is free to mean "not applicable".
const COUNTDOWN_NONE := 255