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,35 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name():
|
||||
return "RewindableRandomNumberGenerator performance tests"
|
||||
|
||||
func test_benchmark() -> void:
|
||||
var rng := RandomNumberGenerator.new()
|
||||
var rrng := RewindableRandomNumberGenerator.new(0)
|
||||
var sum := 0.0
|
||||
|
||||
benchmark("Built-in RNG", func(__):
|
||||
sum += rng.randfn()
|
||||
)\
|
||||
.with_duration(1.0)\
|
||||
.with_batch_size(4096)\
|
||||
.run()
|
||||
|
||||
NetworkMocks.in_rollback(func():
|
||||
NetworkMocks.set_tick(0, 0)
|
||||
|
||||
benchmark("Rewindable RNG on the same tick", func(__):
|
||||
sum += rrng.randfn()
|
||||
)\
|
||||
.with_duration(1.0)\
|
||||
.with_batch_size(4096)\
|
||||
.run()
|
||||
|
||||
benchmark("Rewindable RNG on the changing tick", func(__):
|
||||
NetworkRollback._tick += 1
|
||||
sum += rrng.randfn()
|
||||
)\
|
||||
.with_duration(1.0)\
|
||||
.with_batch_size(4096)\
|
||||
.run()
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://hedy5j0875h6
|
||||
@@ -0,0 +1,47 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name():
|
||||
return "RewindableRandomNumberGenerator"
|
||||
|
||||
func suite():
|
||||
test("should generate the same numbers for the same rollback tick in different loop", func():
|
||||
NetworkMocks.in_rollback(func():
|
||||
var rng := RewindableRandomNumberGenerator.new(0)
|
||||
|
||||
NetworkMocks.set_tick(2, 4)
|
||||
var first_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
NetworkMocks.set_tick(3, 4)
|
||||
var second_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
expect_equal(first_batch, second_batch)
|
||||
)
|
||||
)
|
||||
|
||||
test("should generate different numbers for different ticks", func():
|
||||
NetworkMocks.in_rollback(func():
|
||||
var rng := RewindableRandomNumberGenerator.new(0)
|
||||
|
||||
NetworkMocks.set_tick(2, 4)
|
||||
var first_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
NetworkMocks.set_tick(2, 2)
|
||||
var second_batch := range(4).map(func(__): return rng.randi_range(0, 10))
|
||||
|
||||
expect_not_equal(first_batch, second_batch)
|
||||
)
|
||||
)
|
||||
|
||||
test("different seeds should generate different numbers", func():
|
||||
NetworkMocks.in_rollback(func():
|
||||
NetworkMocks.set_tick(0, 0)
|
||||
|
||||
var first_rng := RewindableRandomNumberGenerator.new(1)
|
||||
var second_rng := RewindableRandomNumberGenerator.new(2)
|
||||
|
||||
var first_batch := range(4).map(func(__): return first_rng.randi_range(0, 10))
|
||||
var second_batch := range(4).map(func(__): return second_rng.randi_range(0, 10))
|
||||
|
||||
expect_not_equal(first_batch, second_batch)
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8b8k164dxjsk
|
||||
@@ -0,0 +1,135 @@
|
||||
extends VestTest
|
||||
|
||||
func get_suite_name() -> String:
|
||||
return "RewindableStateMachine"
|
||||
|
||||
var state_machine: RewindableStateMachine
|
||||
var first_state: RewindableState
|
||||
var other_state: RewindableState
|
||||
|
||||
func before_case(__):
|
||||
state_machine = RewindableStateMachine.new()
|
||||
first_state = mock(RewindableState)
|
||||
other_state = mock(RewindableState)
|
||||
|
||||
# Setup mock answers
|
||||
when(first_state.can_enter).then_return(true)
|
||||
when(first_state.enter).then_answer(func(__): pass)
|
||||
when(first_state.exit).then_answer(func(__): pass)
|
||||
when(first_state.tick).then_answer(func(__): pass)
|
||||
when(first_state.display_enter).then_answer(func(__): pass)
|
||||
when(first_state.display_exit).then_answer(func(__): pass)
|
||||
|
||||
when(other_state.can_enter).then_return(true)
|
||||
when(other_state.enter).then_answer(func(__): pass)
|
||||
when(other_state.exit).then_answer(func(__): pass)
|
||||
when(other_state.tick).then_answer(func(__): pass)
|
||||
when(other_state.display_enter).then_answer(func(__): pass)
|
||||
when(other_state.display_exit).then_answer(func(__): pass)
|
||||
|
||||
# Set state names
|
||||
first_state.name = "First State"
|
||||
other_state.name = "Other State"
|
||||
|
||||
# Add states as children - RSM will pick them up when added
|
||||
# **NOTE**: Make sure to set owner if spawning states manually, otherwise
|
||||
# RSM won't pick them up
|
||||
state_machine.add_child(first_state); first_state.owner = state_machine
|
||||
state_machine.add_child(other_state); other_state.owner = state_machine
|
||||
|
||||
# TODO(vest): TestingSceneTree
|
||||
await Vest._scene_tree.process_frame
|
||||
Vest._scene_tree.root.add_child(state_machine, true)
|
||||
|
||||
func test_should_start_empty():
|
||||
expect_empty(state_machine.state)
|
||||
|
||||
func test_should_notify_new_state_on_enter():
|
||||
capture_signal(first_state.on_enter, 3)
|
||||
|
||||
# Enter first state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Check for event
|
||||
expect_not_empty(get_signal_emissions(first_state.on_enter))
|
||||
expect_empty(get_calls_of(first_state.can_enter))
|
||||
expect_equal(get_calls_of(first_state.enter), [[null, 0]])
|
||||
|
||||
func test_on_enter_should_prevent_transition():
|
||||
other_state.on_enter.connect(
|
||||
func(_new_state, _tick, prevent):
|
||||
prevent.call()
|
||||
)
|
||||
|
||||
# Enter first state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Try to enter second state
|
||||
expect_false(state_machine.transition("Other State"), "Transition should have failed!")
|
||||
expect_equal(state_machine.state, "First State")
|
||||
|
||||
func test_on_exit_should_prevent_transition():
|
||||
first_state.on_exit.connect(
|
||||
func(_new_state, _tick, prevent):
|
||||
prevent.call()
|
||||
)
|
||||
|
||||
# Enter first state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Try to enter second state
|
||||
expect_false(state_machine.transition("Other State"), "Transition should have failed!")
|
||||
expect_equal(state_machine.state, "First State")
|
||||
|
||||
func test_can_enter_should_prevent_transition():
|
||||
state_machine.state = "First State"
|
||||
|
||||
# Register a more specific answer so the mock will use that
|
||||
when(other_state.can_enter)\
|
||||
.with_args([first_state])\
|
||||
.then_return(func(__): return false)
|
||||
|
||||
# Try to enter second state
|
||||
expect_false(state_machine.transition("Other State"), "Transition should have failed!")
|
||||
expect_equal(state_machine.state, "First State")
|
||||
|
||||
func test_should_call_tick():
|
||||
capture_signal(first_state.on_tick, 3)
|
||||
|
||||
# Set state
|
||||
state_machine.transition("First State")
|
||||
|
||||
# Run a rollback tick
|
||||
state_machine._rollback_tick(0.16, 0, true)
|
||||
|
||||
# Tick should have been called
|
||||
expect_equal(get_calls_of(first_state.tick), [[0.16, 0, true]], "Wrong method call!")
|
||||
expect_equal(get_signal_emissions(first_state.on_tick), [[0.16, 0, true]], "Wrong signal!")
|
||||
|
||||
func test_should_notify_display_state():
|
||||
state_machine.state = "First State"
|
||||
|
||||
capture_signal(first_state.on_display_enter, 2)
|
||||
capture_signal(first_state.on_display_exit, 2)
|
||||
capture_signal(other_state.on_display_enter, 2)
|
||||
|
||||
# First loop, display enters First State
|
||||
NetworkMocks.in_network_tick_loop(func():
|
||||
state_machine.transition("First State")
|
||||
)
|
||||
expect_not_empty(get_signal_emissions(first_state.on_display_enter))
|
||||
expect_not_empty(get_calls_of(first_state.display_enter))
|
||||
|
||||
# Second loop, display enters Other State
|
||||
NetworkMocks.in_network_tick_loop(func():
|
||||
state_machine.transition("Other State")
|
||||
)
|
||||
|
||||
expect_not_empty(get_signal_emissions(first_state.on_display_exit))
|
||||
expect_not_empty(get_signal_emissions(other_state.on_display_enter))
|
||||
|
||||
expect_not_empty(get_calls_of(first_state.display_exit))
|
||||
expect_not_empty(get_calls_of(other_state.display_enter))
|
||||
|
||||
func after_case(__):
|
||||
state_machine.queue_free()
|
||||
@@ -0,0 +1 @@
|
||||
uid://cykemuvhqeq2r
|
||||
Reference in New Issue
Block a user