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,18 @@
|
||||
# Rollback Debugger
|
||||
|
||||
A small tool to help visualize and debug what happens during rollback, signal
|
||||
by signal.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||
Open `rollback-debugger.tscn`.
|
||||
|
||||
Replace the *Subject* node with the rollback-enabled node you want to debug.
|
||||
|
||||
Optionally, edit `rollback-debugger.gd` and set the State and Input history
|
||||
fields' values to the initial states after the first `_render_data()` call, to
|
||||
avoid having to paste the same values on each run.
|
||||
|
||||
Editable fields are taken into consideration and applied before every signal.
|
||||
@@ -0,0 +1 @@
|
||||
*
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
@@ -0,0 +1,245 @@
|
||||
[gd_scene load_steps=6 format=3 uid="uid://c3q2g36patykx"]
|
||||
|
||||
[ext_resource type="Script" path="res://examples/rollback-debugger/scripts/rollback-debugger.gd" id="1_2emun"]
|
||||
[ext_resource type="Texture2D" uid="uid://csg26ysqqb4xe" path="res://addons/netfox/icons/rollback-synchronizer.svg" id="2_hjcrm"]
|
||||
[ext_resource type="Script" path="res://examples/rollback-debugger/scripts/subject.gd" id="3_5kvxc"]
|
||||
[ext_resource type="Script" path="res://addons/netfox/rollback/rollback-synchronizer.gd" id="3_0135o"]
|
||||
[ext_resource type="Script" path="res://examples/rollback-debugger/scripts/input.gd" id="4_p1byq"]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="Rollback Debugger" type="Node" parent="."]
|
||||
script = ExtResource("1_2emun")
|
||||
|
||||
[node name="HBoxContainer" type="HBoxContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="History" type="VBoxContainer" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
|
||||
[node name="State History Label" type="Label" parent="HBoxContainer/History"]
|
||||
layout_mode = 2
|
||||
text = "State history"
|
||||
|
||||
[node name="ScrollContainer" type="ScrollContainer" parent="HBoxContainer/History"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Container" type="PanelContainer" parent="HBoxContainer/History/ScrollContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="State History Data" type="TextEdit" parent="HBoxContainer/History/ScrollContainer/Container"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Input History Label" type="Label" parent="HBoxContainer/History"]
|
||||
layout_mode = 2
|
||||
text = "Input history"
|
||||
|
||||
[node name="ScrollContainer2" type="ScrollContainer" parent="HBoxContainer/History"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Container" type="PanelContainer" parent="HBoxContainer/History/ScrollContainer2"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Input History Data" type="TextEdit" parent="HBoxContainer/History/ScrollContainer2/Container"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Data" type="VBoxContainer" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_stretch_ratio = 0.5
|
||||
|
||||
[node name="Has Input" type="HBoxContainer" parent="HBoxContainer/Data"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Has Input Label" type="Label" parent="HBoxContainer/Data/Has Input"]
|
||||
layout_mode = 2
|
||||
text = "Has input:"
|
||||
|
||||
[node name="Has Input Data" type="Label" parent="HBoxContainer/Data/Has Input"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "true"
|
||||
|
||||
[node name="Input Age" type="HBoxContainer" parent="HBoxContainer/Data"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Input Age Label" type="Label" parent="HBoxContainer/Data/Input Age"]
|
||||
layout_mode = 2
|
||||
text = "Input age:"
|
||||
|
||||
[node name="Input Age Data" type="Label" parent="HBoxContainer/Data/Input Age"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "2 ticks"
|
||||
|
||||
[node name="Simset Label" type="Label" parent="HBoxContainer/Data"]
|
||||
layout_mode = 2
|
||||
text = "Simset:"
|
||||
|
||||
[node name="Simset List" type="ItemList" parent="HBoxContainer/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Skipset Label" type="Label" parent="HBoxContainer/Data"]
|
||||
layout_mode = 2
|
||||
text = "Skipset:"
|
||||
|
||||
[node name="Skipset List" type="ItemList" parent="HBoxContainer/Data"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Transmit State Label" type="Label" parent="HBoxContainer/Data"]
|
||||
layout_mode = 2
|
||||
text = "Transmit state:"
|
||||
|
||||
[node name="PanelContainer" type="PanelContainer" parent="HBoxContainer/Data"]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Transmit State Data" type="Label" parent="HBoxContainer/Data/PanelContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="Tools" type="VBoxContainer" parent="HBoxContainer"]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 8
|
||||
|
||||
[node name="Tick Container" type="HBoxContainer" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Network Tick Label" type="Label" parent="HBoxContainer/Tools/Tick Container"]
|
||||
layout_mode = 2
|
||||
text = "Network Tick: "
|
||||
|
||||
[node name="Network Tick Data" type="LineEdit" parent="HBoxContainer/Tools/Tick Container"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "0"
|
||||
|
||||
[node name="Rollback Tick Label" type="Label" parent="HBoxContainer/Tools/Tick Container"]
|
||||
layout_mode = 2
|
||||
text = "Rollback Tick: "
|
||||
|
||||
[node name="Rollback Tick Data" type="LineEdit" parent="HBoxContainer/Tools/Tick Container"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "0"
|
||||
|
||||
[node name="NT Before Loop Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkTime.before_tick_loop"
|
||||
|
||||
[node name="HSeparator5" type="HSeparator" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NT Before Tick Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkTime.before_tick"
|
||||
|
||||
[node name="NT On Tick Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkTime.on_tick"
|
||||
|
||||
[node name="HSeparator6" type="HSeparator" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NR Before Loop Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkRollback.before_loop"
|
||||
|
||||
[node name="HSeparator2" type="HSeparator" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NR Prepare Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkRollback.on_prepare_tick"
|
||||
|
||||
[node name="NR Process Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkRollback.on_process_tick"
|
||||
|
||||
[node name="NR Record Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkRollback.on_record_tick"
|
||||
|
||||
[node name="HSeparator3" type="HSeparator" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NR After Loop" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkRollback.after_loop"
|
||||
|
||||
[node name="HSeparator7" type="HSeparator" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NT After Tick Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkTime.after_tick"
|
||||
|
||||
[node name="HSeparator8" type="HSeparator" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="NT After Tick Loop Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "NetworkTime.after_tick_loop"
|
||||
|
||||
[node name="HSeparator4" type="HSeparator" parent="HBoxContainer/Tools"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Run NR Tick Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Run Rollback tick"
|
||||
|
||||
[node name="Advance Button" type="Button" parent="HBoxContainer/Tools"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Advance tick"
|
||||
|
||||
[node name="Subject" type="Sprite2D" parent="."]
|
||||
z_index = -1
|
||||
position = Vector2(270, 270)
|
||||
texture = ExtResource("2_hjcrm")
|
||||
script = ExtResource("3_5kvxc")
|
||||
|
||||
[node name="Input" type="Node" parent="Subject"]
|
||||
script = ExtResource("4_p1byq")
|
||||
|
||||
[node name="RollbackSynchronizer" type="Node" parent="Subject" node_paths=PackedStringArray("root")]
|
||||
script = ExtResource("3_0135o")
|
||||
root = NodePath("..")
|
||||
state_properties = Array[String]([":position"])
|
||||
input_properties = Array[String](["Input:movement"])
|
||||
@@ -0,0 +1,3 @@
|
||||
extends Node
|
||||
|
||||
var movement := Vector2.ZERO
|
||||
@@ -0,0 +1 @@
|
||||
uid://bmke0hwo4yufm
|
||||
@@ -0,0 +1,174 @@
|
||||
extends Node
|
||||
|
||||
# Data fields
|
||||
@onready var state_history_data := %"State History Data" as TextEdit
|
||||
@onready var input_history_data := %"Input History Data" as TextEdit
|
||||
@onready var network_tick_data := %"Network Tick Data" as LineEdit
|
||||
@onready var rollback_tick_data := %"Rollback Tick Data" as LineEdit
|
||||
@onready var has_input_data := %"Has Input Data" as Label
|
||||
@onready var input_age_data := %"Input Age Data" as Label
|
||||
@onready var simset_list := %"Simset List" as ItemList
|
||||
@onready var skipset_list := %"Skipset List" as ItemList
|
||||
@onready var transmit_state_data := %"Transmit State Data" as Label
|
||||
|
||||
# Tool buttons
|
||||
@onready var nt_before_loop_button := %"NT Before Loop Button" as Button
|
||||
@onready var nt_before_tick_button := %"NT Before Tick Button" as Button
|
||||
@onready var nt_on_tick_button := %"NT On Tick Button" as Button
|
||||
@onready var nr_before_loop_button := %"NR Before Loop Button" as Button
|
||||
@onready var nr_prepare_button := %"NR Prepare Button" as Button
|
||||
@onready var nr_process_button := %"NR Process Button" as Button
|
||||
@onready var nr_record_button := %"NR Record Button" as Button
|
||||
@onready var nr_after_loop_button := %"NR After Loop" as Button
|
||||
@onready var nt_after_tick_button := %"NT After Tick Button" as Button
|
||||
@onready var nt_after_tick_loop_button := %"NT After Tick Loop Button" as Button
|
||||
|
||||
@onready var run_nr_tick_button := %"Run NR Tick Button" as Button
|
||||
@onready var advance_button := %"Advance Button" as Button
|
||||
|
||||
@onready var rollback_synchronizer := self.get_tree().root\
|
||||
.find_children("*", "RollbackSynchronizer", true, false)\
|
||||
.pop_front() as RollbackSynchronizer
|
||||
|
||||
func _ready():
|
||||
if not rollback_synchronizer:
|
||||
OS.alert("No RollbackSynchronizer found! Add one to the scene and run again!")
|
||||
get_tree().quit()
|
||||
|
||||
# Render initial data
|
||||
_render_data()
|
||||
|
||||
# Set defaults
|
||||
input_history_data.text = """{
|
||||
0: { "Input:movement": Vector2(1, 0) },
|
||||
1: { "Input:movement": Vector2(1, 1) },
|
||||
2: { "Input:movement": Vector2(1, 1) },
|
||||
3: { "Input:movement": Vector2(0, 1) },
|
||||
}"""
|
||||
|
||||
# Start NetworkTime but make sure it doesn't tick on its own
|
||||
NetworkTime.start()
|
||||
NetworkTime.set_process(false)
|
||||
|
||||
# Connect signals
|
||||
rollback_synchronizer._on_transmit_state.connect(func(state, tick):
|
||||
transmit_state_data.text = "@%d: %s" % [tick, var_to_str(state)]
|
||||
)
|
||||
|
||||
nt_before_loop_button.pressed.connect(func():
|
||||
NetworkTime.before_tick_loop.emit()
|
||||
)
|
||||
|
||||
nt_before_tick_button.pressed.connect(func():
|
||||
NetworkTime.before_tick.emit(NetworkTime.ticktime, NetworkTime.tick)
|
||||
)
|
||||
|
||||
nt_on_tick_button.pressed.connect(func():
|
||||
NetworkTime.on_tick.emit(NetworkTime.ticktime, NetworkTime.tick)
|
||||
)
|
||||
|
||||
nr_before_loop_button.pressed.connect(func():
|
||||
NetworkRollback.before_loop.emit()
|
||||
)
|
||||
|
||||
nr_prepare_button.pressed.connect(func():
|
||||
NetworkRollback.on_prepare_tick.emit(NetworkRollback.tick)
|
||||
NetworkRollback.after_prepare_tick.emit(NetworkRollback.tick)
|
||||
)
|
||||
|
||||
nr_process_button.pressed.connect(func():
|
||||
NetworkRollback.on_process_tick.emit(NetworkRollback.tick)
|
||||
NetworkRollback._tick += 1
|
||||
)
|
||||
|
||||
nr_record_button.pressed.connect(func():
|
||||
NetworkRollback.on_record_tick.emit(NetworkRollback.tick)
|
||||
)
|
||||
|
||||
nr_after_loop_button.pressed.connect(func():
|
||||
NetworkRollback.after_loop.emit()
|
||||
)
|
||||
|
||||
nt_after_tick_button.pressed.connect(func():
|
||||
NetworkTime.after_tick.emit(NetworkTime.ticktime, NetworkTime.tick)
|
||||
)
|
||||
|
||||
nt_after_tick_loop_button.pressed.connect(func():
|
||||
NetworkTime.after_tick_loop.emit()
|
||||
NetworkTime._tick += 1
|
||||
)
|
||||
|
||||
run_nr_tick_button.pressed.connect(func():
|
||||
NetworkRollback.before_loop.emit()
|
||||
|
||||
NetworkRollback.on_prepare_tick.emit(NetworkRollback.tick)
|
||||
NetworkRollback.after_prepare_tick.emit(NetworkRollback.tick)
|
||||
NetworkRollback.on_process_tick.emit(NetworkRollback.tick)
|
||||
NetworkRollback._tick += 1
|
||||
NetworkRollback.on_record_tick.emit(NetworkRollback.tick)
|
||||
|
||||
NetworkRollback.after_loop.emit()
|
||||
)
|
||||
|
||||
advance_button.pressed.connect(func():
|
||||
NetworkRollback._tick += 1
|
||||
)
|
||||
|
||||
# Update UI after any button press
|
||||
var buttons = get_parent().find_children("*", "Button")
|
||||
for button in buttons:
|
||||
(button as Button).button_down.connect(_read_data)
|
||||
(button as Button).button_up.connect(_render_data)
|
||||
|
||||
func _render_data():
|
||||
network_tick_data.text = str(NetworkTime.tick)
|
||||
rollback_tick_data.text = str(NetworkRollback.tick)
|
||||
|
||||
state_history_data.text = _serialize_history(rollback_synchronizer._states)
|
||||
input_history_data.text = _serialize_history(rollback_synchronizer._inputs)
|
||||
|
||||
if rollback_synchronizer.has_input():
|
||||
has_input_data.text = "true"
|
||||
input_age_data.text = str(rollback_synchronizer.get_input_age())
|
||||
else:
|
||||
has_input_data.text = "false"
|
||||
input_age_data.text = "?"
|
||||
|
||||
simset_list.clear()
|
||||
for sim_entry in rollback_synchronizer._simset.values():
|
||||
simset_list.add_item(str(sim_entry), null, false)
|
||||
|
||||
skipset_list.clear()
|
||||
for skip_entry in rollback_synchronizer._skipset.values():
|
||||
skipset_list.add_item(str(skip_entry), null, false)
|
||||
|
||||
func _read_data():
|
||||
if network_tick_data.text.is_valid_int():
|
||||
NetworkTime._tick = network_tick_data.text.to_int()
|
||||
|
||||
if rollback_tick_data.text.is_valid_int():
|
||||
NetworkRollback._tick = rollback_tick_data.text.to_int()
|
||||
|
||||
rollback_synchronizer._states = _parse_history(state_history_data.text)
|
||||
rollback_synchronizer._inputs = _parse_history(input_history_data.text)
|
||||
|
||||
func _serialize_history(history: _PropertyHistoryBuffer) -> String:
|
||||
var result = PackedStringArray()
|
||||
|
||||
for tick in history.ticks():
|
||||
var snapshot := history.get_snapshot(tick)
|
||||
result.append("\t%d: %s" % [tick, var_to_str(snapshot.as_dictionary()).replace("\n", "")])
|
||||
|
||||
return "{\n%s\n}" % [",\n".join(result)]
|
||||
|
||||
func _parse_history(history_string: String) -> _PropertyHistoryBuffer:
|
||||
var result_data = str_to_var(history_string)
|
||||
if not result_data is Dictionary:
|
||||
return _PropertyHistoryBuffer.new()
|
||||
|
||||
var result := _PropertyHistoryBuffer.new()
|
||||
for tick in result_data.keys():
|
||||
var snapshot := _PropertySnapshot.from_dictionary(result_data[tick])
|
||||
result.set_snapshot(tick, snapshot)
|
||||
|
||||
return result
|
||||
@@ -0,0 +1 @@
|
||||
uid://bbyunye70km5j
|
||||
@@ -0,0 +1,13 @@
|
||||
extends Sprite2D
|
||||
|
||||
@export var move_speed := 16.
|
||||
|
||||
@onready var input := $Input
|
||||
@onready var rollback_synchronizer := $RollbackSynchronizer as RollbackSynchronizer
|
||||
|
||||
func _rollback_tick(_dt, _t, _if):
|
||||
if not rollback_synchronizer.has_input() or rollback_synchronizer.get_input_age() > 0:
|
||||
rollback_synchronizer.ignore_prediction(self)
|
||||
return
|
||||
|
||||
position += input.movement.normalized() * move_speed
|
||||
@@ -0,0 +1 @@
|
||||
uid://dmqopdmgukewq
|
||||
Reference in New Issue
Block a user