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:
2026-07-02 20:55:20 -04:00
parent ce39b237c3
commit b0c83af092
4416 changed files with 57418 additions and 902676 deletions
+113 -49
View File
@@ -1,70 +1,134 @@
extends RefCounted
class_name _HistoryBuffer
# Maps ticks (int) to arbitrary data
var _buffer: Dictionary = {}
# Maps ticks (int) to arbitrary data, stored in a sliding ring buffer
func get_snapshot(tick: int):
if _buffer.has(tick):
return _buffer[tick]
else:
return null
var _capacity := 64
var _buffer := []
var _previous := []
func set_snapshot(tick: int, data):
_buffer[tick] = data
var _tail := 0
var _head := 0
func get_buffer() -> Dictionary:
return _buffer
static func of(capacity: int, data: Dictionary) -> _HistoryBuffer:
var history_buffer := _HistoryBuffer.new(capacity)
for idx in data:
history_buffer.set_at(idx, data[idx])
return history_buffer
func get_earliest_tick() -> int:
return _buffer.keys().min()
func _init(capacity: int = 64):
_capacity = capacity
_buffer.resize(_capacity)
_previous.resize(_capacity)
func get_latest_tick() -> int:
return _buffer.keys().max()
func duplicate(deep: bool = false) -> _HistoryBuffer:
var buffer := _HistoryBuffer.new(_capacity)
func get_closest_tick(tick: int) -> int:
if _buffer.has(tick):
return tick
buffer._buffer = _buffer.duplicate(deep)
buffer._previous = _previous.duplicate()
buffer._tail = _tail
buffer._head = _head
if _buffer.is_empty():
return -1
return buffer
var earliest_tick = get_earliest_tick()
if tick < earliest_tick:
return earliest_tick
func push(value: Variant) -> void:
_buffer[_head % _capacity] = value
_previous[_head % _capacity] = _head
_head += 1
_tail += maxi(0, size() - capacity())
var latest_tick = get_latest_tick()
if tick > latest_tick:
return latest_tick
func pop() -> Variant:
assert(is_not_empty(), "History buffer is empty!")
return _buffer.keys() \
.filter(func (key): return key < tick) \
.max()
var value = _buffer[_tail % _capacity]
_tail += 1
return value
func get_history(tick: int):
var closest_tick = get_closest_tick(tick)
return _buffer.get(closest_tick)
func set_at(at: int, value: Variant) -> void:
# Why does this need so many branches?
if is_empty():
# Buffer is empty, jump to specified index
_tail = at
_head = at
push(value)
elif at < _head - capacity():
# Trying to set something that would wrap back around and overwrite
# current data
return
elif at == _head:
# Simply adding a new item
push(value)
elif at < _head:
_buffer[at % _capacity] = value
# Update prev-buffer
for i in range(at, _head):
if _previous[i % _capacity] == i:
break
_previous[i % _capacity] = at
_tail = mini(_tail, at)
elif at >= _head + _capacity:
# We're leaving all data behind
_tail = at
_head = at
func trim(earliest_tick_to_keep: int):
var ticks := _buffer.keys()
for tick in ticks:
if tick < earliest_tick_to_keep:
_buffer.erase(tick)
_previous.fill(null)
_buffer.fill(null)
func clear():
_buffer.clear()
push(value)
elif at >= _head:
# Skipping forward a bit
var previous := _head - 1
while _head < at:
_previous[_head % _capacity] = previous
_head += 1
_tail += maxi(0, size() - _capacity)
push(value)
func has_at(at: int) -> bool:
if is_empty(): return false
if at < _head - capacity(): return false
if at >= _head: return false
return _previous[at % _capacity] == at
func get_at(at: int, default: Variant = null) -> Variant:
if not has_at(at):
return default
return _buffer[at % _capacity]
func has_latest_at(at: int) -> bool:
if is_empty(): return false
if at < _tail: return false
return true
func size() -> int:
return _buffer.size()
return _head - _tail
func capacity() -> int:
return _capacity
func get_earliest_index() -> int:
return _tail
func get_latest_index() -> int:
return _head - 1
func get_latest_index_at(at: int) -> int:
if not has_latest_at(at):
return -1
if at >= _head:
return get_latest_index()
return _previous[at % _capacity]
func get_latest_at(at: int) -> Variant:
return get_at(get_latest_index_at(at))
func clear():
_tail = _head
func is_empty() -> bool:
return _buffer.is_empty()
return size() == 0
func has(tick) -> bool:
return _buffer.has(tick)
func ticks() -> Array:
return _buffer.keys()
func erase(tick):
_buffer.erase(tick)
func is_not_empty() -> bool:
return not is_empty()