Files
tactical-shooter/docs/netfox/guides/network-command-server.md
T
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

3.7 KiB

NetworkCommandServer

Implements a simpler, lightweight alternative to RPCs. Provided as an autoload.

Commands consist of a single byte for ID, and the raw binary data. The ID lets the receiving peer decide what to execute, with the binary data serving as the input.

Being a simpler construct makes commands a good fit for regular, fundamental operations. For example, commands internally are used for time synchronization, or synchronizing state and input between peers.

Commands are, by default, transmitted over regular RPCs. To use less data, commands can also be transmitted as raw packets, using SceneMultiplayer.send_bytes(). This is an opt-in feature - if the game is already using SceneMultiplayer.send_bytes(), it needs to be aware of commands, and must check each packet whether it's a command or one of its own packets. To check if a packet is a command, use is_command_packet().

Implementing custom commands

Custom commands can be registered with the NetworkCommandServer, using register_command(). This returns a Command object that provides a convenient interface.

During registration, a callback must be provided, that will be ran when the command is received.

Commands can be sent using its send() method.

@onready var cmd_message := NetworkCommandServer.register_command(handle_message, MultiplayerPeer.TRANSFER_MODE_UNRELIABLE)

func handle_message(sender: int, data: PackedByteArray) -> void:
  var message := data.get_string_from_utf8()
  print("#%d: %s" % [sender, message])

func _ready() -> void:
  cmd_message.send("Hello, world!".to_utf8_buffer())

!!!tip It is recommended to setup commands once, at game start. When registering commands from autoloads, make sure they run after netfox's autoloads.

Differences compared to RPCs

Commands are a fundamentally simpler constructs compared to RPCs.

Maximum 256 commands

Commands are limited to 256 indices - make sure to not register more than that. Some commands are registered by netfox on startup as well.

This limitation also makes commands a poor fit for registering dynamically. Dynamic registrations often mean registering commands as certain nodes or objects are created. This, in turn, makes it difficult to place an upper bound on the number of commands needed, which can conflict with this limitation.

Commands are not tied to any node

Commands do not refer to any specific node or object in their content. They only contain a command index. Even though the API encapsulates this into Command objects, it is completely feasible to have different nodes handle the same command on different peers ( if the game is built as different Godot projects ).

Commands do not track authority

Any peer can send any command to any other peer. It is the receiving peer's responsibility to check whether the sender is allowed to send such a command or not.

Commands do not have arguments

To stay lightweight and to give maximum control, commands contain raw bytes only, no arguments.

In general, this can be worked around by wrapping the arguments in an array and converting it using var_to_bytes() and bytes_to_var().

However, for cases where bandwidth matters, this allows users to encode data in a way that fits best.

Settings

netfox ▸ General ▸ Use Raw Commands

When enabled, netfox will transmit commands as raw packets, instead of RPCs.