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>
This commit is contained in:
@@ -85,7 +85,6 @@ var display_tick: int:
|
||||
## with input latency higher than network latency.
|
||||
## [br][br]
|
||||
## [i]read-only[/i], you can change this in the project settings
|
||||
|
||||
var input_delay: int:
|
||||
get:
|
||||
return _input_delay
|
||||
@@ -100,7 +99,6 @@ var input_delay: int:
|
||||
## in transmission, the next (n-1) packets will contain the data for it.
|
||||
## [br][br]
|
||||
## [i]read-only[/i], you can change this in the project settings
|
||||
|
||||
var input_redundancy: int:
|
||||
get:
|
||||
return max(1, _input_redundancy)
|
||||
@@ -168,7 +166,9 @@ var _rollback_stage: String = ""
|
||||
var _is_rollback: bool = false
|
||||
var _simulated_nodes: _Set = _Set.new()
|
||||
var _mutated_nodes: Dictionary = {}
|
||||
var _input_submissions: Dictionary = {}
|
||||
|
||||
var _earliest_input := -1
|
||||
var _earliest_state := -1
|
||||
|
||||
const _STAGE_BEFORE := "B"
|
||||
const _STAGE_PREPARE := "P"
|
||||
@@ -268,33 +268,44 @@ func is_just_mutated(target: Object, p_tick: int = tick) -> bool:
|
||||
return false
|
||||
|
||||
## Register that a node has submitted its input for a specific tick
|
||||
func register_input_submission(root_node: Node, tick: int) -> void:
|
||||
if not _input_submissions.has(root_node):
|
||||
_input_submissions[root_node] = tick
|
||||
else:
|
||||
_input_submissions[root_node] = maxi(_input_submissions[root_node], tick)
|
||||
## @deprecated
|
||||
func register_rollback_input_submission(_node: Node, _tick: int) -> void:
|
||||
pass
|
||||
|
||||
## Get the latest input tick submitted by a specific root node
|
||||
## Get the latest input tick submitted for a specific node
|
||||
## [br][br]
|
||||
## Returns [code]-1[/code] if no input was submitted for the node, ever.
|
||||
func get_latest_input_tick(root_node: Node) -> int:
|
||||
if _input_submissions.has(root_node):
|
||||
return _input_submissions[root_node]
|
||||
return -1
|
||||
func get_latest_input_tick(node: Node) -> int:
|
||||
var input_nodes := RollbackSimulationServer._get_inputs_of(node)
|
||||
var reference_tick := NetworkTime.tick
|
||||
|
||||
return NetworkHistoryServer.get_latest_input_for(input_nodes, reference_tick)
|
||||
|
||||
## Check if a node has submitted input for a specific tick (or later)
|
||||
func has_input_for_tick(root_node: Node, tick: int) -> bool:
|
||||
return _input_submissions.has(root_node) and _input_submissions[root_node] >= tick
|
||||
func has_input_for_tick(node: Node, tick: int) -> bool:
|
||||
var latest_input := get_latest_input_tick(node)
|
||||
return latest_input != -1 and latest_input >= tick
|
||||
|
||||
## Free all input submission data for a node
|
||||
## [br][br]
|
||||
## Use this once the node is freed.
|
||||
func free_input_submission_data_for(root_node: Node) -> void:
|
||||
_input_submissions.erase(root_node)
|
||||
## @deprecated
|
||||
func free_input_submission_data_for(_node: Node) -> void:
|
||||
pass
|
||||
|
||||
func _get_rollback_method(object: Object) -> Callable:
|
||||
return object._rollback_tick
|
||||
|
||||
func _ready():
|
||||
NetfoxLogger.register_tag(_get_rollback_tag)
|
||||
NetworkTime.after_tick_loop.connect(_rollback)
|
||||
NetworkTime.after_tick.connect(func(_dt, tick):
|
||||
NetworkHistoryServer._record_rollback_input(tick + input_delay)
|
||||
NetworkSynchronizationServer._synchronize_input(tick + input_delay)
|
||||
)
|
||||
|
||||
NetworkSynchronizationServer._on_input.connect(_handle_input)
|
||||
NetworkSynchronizationServer._on_state.connect(_handle_state)
|
||||
|
||||
func _exit_tree():
|
||||
NetfoxLogger.free_tag(_get_rollback_tag)
|
||||
@@ -313,6 +324,17 @@ func _rollback() -> void:
|
||||
_resim_from = NetworkTime.tick
|
||||
before_loop.emit()
|
||||
|
||||
# Figure out where to start rollback from
|
||||
var range_source = "notif"
|
||||
if _earliest_input >= 0 and _earliest_input <= _resim_from:
|
||||
range_source = "earliest input"
|
||||
_resim_from = _earliest_input
|
||||
if _earliest_state >= 0 and _earliest_state <= _resim_from:
|
||||
range_source = "latest state"
|
||||
_resim_from = _earliest_state
|
||||
_resim_from = mini(_resim_from, NetworkTime.tick - 1)
|
||||
_logger.trace("Simulating range @%d>@%d using %s", [_resim_from, NetworkTime.tick, range_source])
|
||||
|
||||
# Only set _is_rollback *after* emitting before_loop
|
||||
_is_rollback = true
|
||||
_rollback_stage = _STAGE_BEFORE
|
||||
@@ -331,6 +353,9 @@ func _rollback() -> void:
|
||||
)
|
||||
from = NetworkTime.tick - history_limit
|
||||
|
||||
_earliest_input = -1
|
||||
_earliest_state = -1
|
||||
|
||||
# for tick in from .. to:
|
||||
_rollback_from = from
|
||||
_rollback_to = to
|
||||
@@ -343,6 +368,8 @@ func _rollback() -> void:
|
||||
# Restore input and state for tick
|
||||
_rollback_stage = _STAGE_PREPARE
|
||||
on_prepare_tick.emit(tick)
|
||||
NetworkHistoryServer._restore_rollback_input(tick)
|
||||
NetworkHistoryServer._restore_rollback_state(tick)
|
||||
after_prepare_tick.emit(tick)
|
||||
|
||||
# Simulate rollback tick
|
||||
@@ -353,20 +380,43 @@ func _rollback() -> void:
|
||||
# If not: Latest input >= tick >= Earliest input
|
||||
_rollback_stage = _STAGE_SIMULATE
|
||||
on_process_tick.emit(tick)
|
||||
RollbackSimulationServer.simulate(NetworkTime.ticktime, tick)
|
||||
after_process_tick.emit(tick)
|
||||
|
||||
# Record state for tick + 1
|
||||
_rollback_stage = _STAGE_RECORD
|
||||
on_record_tick.emit(tick + 1)
|
||||
NetworkHistoryServer._record_rollback_state(tick + 1)
|
||||
NetworkSynchronizationServer._synchronize_state(tick + 1)
|
||||
|
||||
# Restore display state
|
||||
_rollback_stage = _STAGE_AFTER
|
||||
after_loop.emit()
|
||||
NetworkHistoryServer._restore_rollback_state(display_tick)
|
||||
RollbackSimulationServer._trim_ticks_simulated(history_start)
|
||||
|
||||
# Cleanup
|
||||
_mutated_nodes.clear()
|
||||
_is_rollback = false
|
||||
|
||||
func _handle_input(snapshot: _Snapshot):
|
||||
if snapshot.is_empty():
|
||||
return
|
||||
if _earliest_input < 0 or snapshot.tick < _earliest_input:
|
||||
_logger.trace("Ingested input @%d, earliest @%d->@%d", [snapshot.tick, _earliest_input, snapshot.tick])
|
||||
_earliest_input = snapshot.tick
|
||||
else:
|
||||
_logger.trace("Ingested input @%d, earliest @%d->@%d", [snapshot.tick, _earliest_input, _earliest_input])
|
||||
|
||||
func _handle_state(snapshot: _Snapshot):
|
||||
if snapshot.is_empty():
|
||||
return
|
||||
if _earliest_state < 0 or snapshot.tick < _earliest_state:
|
||||
_logger.trace("Ingested state @%d, latest @%d->@%d", [snapshot.tick, _earliest_state, snapshot.tick])
|
||||
_earliest_state = snapshot.tick
|
||||
else:
|
||||
_logger.trace("Ingested state @%d, latest @%d->@%d", [snapshot.tick, _earliest_state, _earliest_state])
|
||||
|
||||
# Insight 1:
|
||||
# state(x) = simulate(state(x - 1), input(x - 1))
|
||||
# state(x + 1) = simulate(state(x), input(x))
|
||||
|
||||
Reference in New Issue
Block a user