Files
shawn b0c83af092 Fresh start: replace with naxIO/netfox-cs-sample foundation
Complete replacement of the tactical-shooter project with the
netfox-cs-sample (MIT) — a CS 1.6 inspired multiplayer FPS built
with Godot 4 and netfox.

## What's new
- Full CS-style gameplay: teams (T/CT), rounds, economy, buy menu
- 6 weapons: Knife, Glock, USP, AK-47, M4A1, AWP
- Bomb plant/defuse with 2 bombsites
- Flashbang & smoke grenades
- Proper netfox rollback netcode at 64 tick
- Network popup UI for host/join
- HUD, crosshair, round timer, scoreboard
- All netfox singletons registered as autoloads (works in exported builds)

## Architecture
- Listen-server (host from client, no dedicated server binary)
- Multiplayer-fps game lives at examples/multiplayer-fps/
- Netfox addons registered as autoloads for exported build compat
- Godot 4.7 with Forward+ renderer

## Removed
- Old headless-server architecture (client_main, server_main, player.gd, etc.)
- Custom netfox bootstrap with ENet fallback
- Old ChaffGames FPS template (2,420 lines, 844 KB)
- SimulationServer GDExtension stub
- Godot-jolt physics (netfox sample uses default Godot physics)
- Duplicate weapon_data.gd, anti_cheat.gd, round_manager.gd, etc.
- Server browser API Python venv (87 MB)
- test_range map and modular assets

## Preserved
- Git history
- Server config at config/default_server_config.cfg
- Windows export preset
- Build directory (gitignored)

Co-authored-by: naxIO <naxIO@users.noreply.github.com>
2026-07-02 20:55:20 -04:00

117 lines
4.1 KiB
Markdown

# PacketHandshake
Singleton implementing handshake over UDP.
The point of the handshake itself is to confirm two-way connection between
two parties - i.e. both parties can receive message from the other and
receive acknowledgement from the other that their messages have arrived.
This is an important step before establishing connection for actual game
play, as this lets both the client's and server's routers ( if any ) know
that traffic is expected and should be let through.
## NAT punchthrough
Most players are behind a router. Routers are directly connected to the
internet, and protect machines behind them from unwanted traffic.
When routers receive packets from an unknown source, those packets are rejected
and don't reach the player's device. When data was sent to that address first,
routers see traffic as a reply and allow incoming data.
To take an example, if a random PC starts sending traffic your way, the router
will reject it. If you send data to the host behind godotengine.org, your
router will allow incoming traffic from it. Otherwise, you wouldn't be able to
open the website in your browser, as the incoming HTTP response would be
rejected.
This can be used to our advantage. If both players start sending traffic
towards eachother, eventually the routers will assume it's a response to some
request and allow the traffic.
> This is a very simplified description of how routers work. NAT punchthrough
> does not always work. For further reading, see [Network address translation].
## Handshake process
To confirm two-way connectivity, a string is sent back and forth, encoding the
player's knowledge about the connection:
The *Read flag* is set once we have received data from the other player.
The *Write flag* is set once we send data to the other player. Since data is
always sent, this flag is always set.
The *Duplex flag* is set when we have received data from the other player
knowing that they have also received data from us. This means that data flows
both ways.
The handshake process is successful when both players have the *Duplex flag*
set *and* both players know that the other player has the *Duplex flag* set.
Each flag is encoded as its specific character or a hyphen. The encoded string
is prepended with a dollar sign. For example:
* *$rw-* means that we have sent and received data from the other player
* *$rwx* means that the *read*, *write*, and *duplex* flags are all set
Here's the handshake process illustrated:
```puml
@startuml
actor "Player A" as A
entity "Router A" as RA
boundary Internet as Net
entity "Router B" as RB
actor "Player B" as B
A ->x RB : $-w-
note over RB: Packet denied
B ->x RA : $-w-
note over RA: Packet denied
note over RA, RB: NAT table updated on both routers
A -> B: $-w-
note over RB: Packed allowed
B -> A: $-w-
note over RB: Packed allowed
A -> B: $rwx
B -> A: $rwx
note over Net #lightgreen: Connection established
@enduml
```
## Handshake over PacketPeer
To run the handshake over raw UDP, call `PacketHandshake.over_packet_peer()`. The
specified PacketPeer will be used to send data until two-way connectivity is
confirmed or the timeout is reached. Between every packet sent, it takes a
short pause.
!!!note
The PacketPeer must already be configured with a target address.
## Handshake over ENet
If the game is already running, the handshake must be done over the already
active connection. For this case, use `PacketHandshake.over_enet_peer()`. If
the [ENetMultiplayerPeer] is not accessible from where you want to do the
handshake, use `PacketHandshake.over_enet()`.
This connection can't be used to receive custom packets, only to send them. So
the target address will be spammed with traffic confirming two-way connectivity
until timeout. Handshake will always be considered successful.
If the connectivity exists, players will simply connect. Otherwise,
connectivity will fail as expected, regardless of the handshake results.
[Network address translation]: https://en.wikipedia.org/wiki/Network_address_translation
[ENetMultiplayerPeer]: https://docs.godotengine.org/en/4.1/classes/class_enetmultiplayerpeer.html