b0c83af092
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>
152 lines
5.9 KiB
Markdown
152 lines
5.9 KiB
Markdown
# RollbackSynchronizer
|
|
|
|
Manages state during the network rollback loop by hooking into
|
|
[NetworkRollback] events. Simulates nodes as required during rollback.
|
|
|
|
To read more on best practices, see [Rollback caveats].
|
|
|
|
## Configuring state and input
|
|
|
|
To use *RollbackSynchronizer*, add it as a child to the target node, specify
|
|
the root node, and configure which properties to manage:
|
|
|
|

|
|
|
|
*Root* specifies the root node for resolving state and input properties. Best
|
|
practice dictates to add *RollbackSynchronizer* under its target, so *Root*
|
|
will most often be the *RollbackSynchronizer*'s parent node.
|
|
|
|
*State properties* are recorded for each tick and restored during rollback. For
|
|
state, the server is the ultimate authority. Make sure that nodes containing
|
|
state properties are owned by the server.
|
|
|
|
*Full state interval* specifies how many ticks to wait between full states. If
|
|
diff states are enabled, full states are only sent at specific intervals, to
|
|
make sure that peers always have the correct state data. *Only considered if
|
|
diff states are enabled.*
|
|
|
|
*Diff ack interval* specifies how many ticks to wait between acknowledging diff
|
|
states. Setting this to lower non-zero values may result in more bandwidth
|
|
savings on non-changing properties, but this can be outweighed by the increased
|
|
number of ack messages. *Only considered if diff states are enabled.*
|
|
|
|
See [diff states](#diff-states) for more on how the above two settings are
|
|
used.
|
|
|
|
*Input properties* are gathered for each player and sent to the server to use
|
|
for simulation. Make sure that nodes containing input properties are owned by
|
|
their respective players.
|
|
|
|
See [Property paths] on how to specify properties.
|
|
|
|
*Enable input broadcast* toggles whether input properties are broadcast to all
|
|
peers, or only to the server. The default is *true* to support legacy
|
|
behaviour. It is recommended to turn this off to lower bandwidth and lessen the
|
|
attack surface for cheating.
|
|
|
|
!!!warning
|
|
It is not recommended to have both state and input properties on the same
|
|
node. Since nodes with state belong to the server, and nodes with input
|
|
belong to the player, it is difficult to separate ownership on the same
|
|
node.
|
|
|
|
## Writing rollback-aware scripts
|
|
|
|
During setup, *RollbackSynchronizer* finds all the rollback-aware nodes under
|
|
the specified *root*. During rollback, it will call all the rollback-aware
|
|
nodes to simulate new state.
|
|
|
|
To learn about rollback-awareness, see [NetworkRollback].
|
|
|
|
In short, implement `_rollback_tick` in your scripts:
|
|
|
|
```gdscript
|
|
extends CharacterBody3D
|
|
|
|
@export var speed = 4.0
|
|
@export var input: PlayerInput
|
|
|
|
func _rollback_tick(delta, tick, is_fresh):
|
|
velocity = input.movement.normalized() * speed
|
|
|
|
velocity *= NetworkTime.physics_factor
|
|
move_and_slide()
|
|
velocity /= NetworkTime.physics_factor
|
|
```
|
|
|
|
Note the usage of `physics_factor` - this is explained in [Rollback caveats].
|
|
|
|
|
|
## Single fire events
|
|
|
|
The first time a rollback tick is processed, the `is_fresh` parameter is set to
|
|
`true`. This can be used to trigger animations or sounds without them being
|
|
repeated each rollback event.
|
|
|
|
For example to improve the client side experience a spell or weapon can play
|
|
its activating sounds and animation immediately and then proceed to complete
|
|
the action once server confirmation is received.
|
|
|
|
## Changing configuration
|
|
|
|
*RollbackSynchronizer* has to do some setup work whenever the state or the
|
|
input properties change.
|
|
|
|
By default, this work is done upon instantiation. If you need to change state
|
|
or input properties during runtime, make sure to call `process_settings()`,
|
|
otherwise *RollbackSynchronizer* won't apply the changes.
|
|
|
|
!!! warning
|
|
While changing configuration after instantiation is possible, it is not
|
|
recommended. You may get away with it if the configuration change happens in a
|
|
few ticks after instantiation. For longer periods, experiment at your own risk.
|
|
|
|
## Changing ownership
|
|
|
|
The setup work above is also needed whenever the multiplayer authority changes
|
|
of any of the nodes that have a state- or input property.
|
|
|
|
Changing authority during gameplay is supported. Make sure to call
|
|
`process_authority()` on all peers at the same time, to ensure they're on sync
|
|
about ownership.
|
|
|
|
This method is called automatically during instantiation and whenever
|
|
`process_settings()` is called.
|
|
|
|
---
|
|
|
|
When *only* multiplayer authority changes, call `process_authority()`. When the
|
|
configured state- or input properties change ( i.e. different properties need
|
|
to be synced ), call `process_settings()`.
|
|
|
|
## Diff states
|
|
|
|
When diff states are enabled in the [rollback settings], netfox will attempt to
|
|
save bandwidth by only sending state properties that have changed.
|
|
|
|
These changes are always based on a tick that the receiving peer has confirmed
|
|
it already has. Basically we don't want to send changes compared to a tick that
|
|
the peer has no knowledge about.
|
|
|
|
Peers notify the host of which ticks they know about by *acknowledging* ( or
|
|
ack'ing ) ticks. This acknowledging has two flavors.
|
|
|
|
The first flavor is *full states*. These states contain all the state data,
|
|
regardless of what changed and what has stayed the same. These ensure that
|
|
peers have all the state data for a given tick. Once a full state is received,
|
|
the receiving peer acknowledges that tick over a reliable channel.
|
|
|
|
The second flavor is *diff states*. Peers may also acknowledge ticks after
|
|
receiving a diff state, meaning that they have reconstructed the given state
|
|
from a known earlier state and the diff state received. These are acknowledged
|
|
over an unreliable channel. By using an unreliable channel, we can acknowledge
|
|
diff states more often without causing any hiccups in network traffic.
|
|
|
|
When diff states are disabled, netfox will always send full state data for all
|
|
ticks.
|
|
|
|
[Rollback caveats]: ../tutorials/rollback-caveats.md
|
|
[NetworkRollback]: ../guides/network-rollback.md
|
|
[Property paths]: ../guides/property-paths.md
|
|
[rollback settings]: ../guides/network-rollback.md#settings
|