Files
tactical-shooter/test/netfox.extras/rewindable-state-machine.test.gd
T
shawn b0c83af092 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>
2026-07-02 20:55:20 -04:00

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()