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:
@@ -0,0 +1,121 @@
|
||||
@tool
|
||||
extends Node
|
||||
class_name PredictiveSynchronizer
|
||||
|
||||
## Similar to [RollbackSynchronizer], this class manages local variables in a
|
||||
## rollback context for predictive simulation without networking.
|
||||
##
|
||||
## This is a simplified version that focuses on local state management.
|
||||
## [br][br]
|
||||
## Like [RollbackSynchronizer], it automatically discovers nodes
|
||||
## with a [code]_rollback_tick(delta: float, tick: int)[/code]
|
||||
## method and calls them during the prediction phase.
|
||||
|
||||
## The root node for resolving node paths in properties. Defaults to the parent
|
||||
## node.
|
||||
@export var root: Node = get_parent()
|
||||
|
||||
@export_group("State")
|
||||
## Properties that define the game state.
|
||||
## [br][br]
|
||||
## State properties are recorded for each tick and restored during rollback.
|
||||
## State is restored before every rollback tick, and recorded after simulating
|
||||
## the tick.
|
||||
@export var state_properties: Array[String]
|
||||
|
||||
var _state_properties := _PropertyPool.new()
|
||||
var _sim_nodes: Array[Node] = []
|
||||
|
||||
var _properties_dirty: bool = false
|
||||
|
||||
## Process settings.
|
||||
##
|
||||
## Call this after any change to configuration.
|
||||
func process_settings() -> void:
|
||||
# Deregister all nodes we've registered previously
|
||||
for subject in _state_properties.get_subjects():
|
||||
NetworkHistoryServer.deregister(subject)
|
||||
|
||||
for node in _sim_nodes:
|
||||
RollbackSimulationServer.deregister_node(node)
|
||||
|
||||
# Gather all prediction-aware nodes to call during prediction ticks
|
||||
_sim_nodes = root.find_children("*")
|
||||
_sim_nodes.push_front(root)
|
||||
_sim_nodes = _sim_nodes.filter(func(it): return NetworkRollback.is_rollback_aware(it))
|
||||
_sim_nodes.erase(self)
|
||||
|
||||
# Keep history of state properties
|
||||
_state_properties.set_from_paths(root, state_properties)
|
||||
for subject in _state_properties.get_subjects():
|
||||
for property in _state_properties.get_properties_of(subject):
|
||||
NetworkHistoryServer.register_rollback_state(subject, property)
|
||||
|
||||
# Simulated notes to participate in rollback
|
||||
for node in _sim_nodes:
|
||||
RollbackSimulationServer.register(NetworkRollback._get_rollback_method(node))
|
||||
|
||||
func _ready() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if not NetworkTime.is_initial_sync_done():
|
||||
# Wait for time sync to complete
|
||||
await NetworkTime.after_sync
|
||||
|
||||
process_settings.call_deferred()
|
||||
|
||||
func _enter_tree() -> void:
|
||||
if Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
if not NetworkTime.is_initial_sync_done():
|
||||
# Wait for time sync to complete
|
||||
await NetworkTime.after_sync
|
||||
process_settings.call_deferred()
|
||||
|
||||
func _reprocess_settings() -> void:
|
||||
if not _properties_dirty or Engine.is_editor_hint():
|
||||
return
|
||||
|
||||
_properties_dirty = false
|
||||
process_settings()
|
||||
|
||||
## Add a state property.
|
||||
## [br][br]
|
||||
## Settings will be automatically updated. The [param node] may be a string or
|
||||
## [NodePath] pointing to a node, or an actual [Node] instance. If the given
|
||||
## property is already tracked, this method does nothing.
|
||||
func add_state(node: Variant, property: String):
|
||||
var property_path := PropertyEntry.make_path(root, node, property)
|
||||
if not property_path or state_properties.has(property_path):
|
||||
return
|
||||
|
||||
state_properties.push_back(property_path)
|
||||
_properties_dirty = true
|
||||
_reprocess_settings.call_deferred()
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_EDITOR_PRE_SAVE:
|
||||
update_configuration_warnings()
|
||||
if what == NOTIFICATION_PREDELETE:
|
||||
for node in _sim_nodes:
|
||||
RollbackSimulationServer.deregister_node(node)
|
||||
for subject in _state_properties.get_subjects():
|
||||
NetworkHistoryServer.deregister(subject)
|
||||
|
||||
func _get_configuration_warnings() -> PackedStringArray:
|
||||
if not root:
|
||||
root = get_parent()
|
||||
|
||||
# Explore state properties
|
||||
if not root:
|
||||
return ["No valid root node found!"]
|
||||
|
||||
var result := PackedStringArray()
|
||||
result.append_array(_NetfoxEditorUtils.gather_properties(root, "_get_rollback_state_properties",
|
||||
func(node, prop):
|
||||
add_state(node, prop)
|
||||
))
|
||||
|
||||
return result
|
||||
Reference in New Issue
Block a user