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>
136 lines
4.3 KiB
GDScript
136 lines
4.3 KiB
GDScript
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()
|