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,159 @@
|
||||
extends Node
|
||||
|
||||
@export var player_scene: PackedScene
|
||||
|
||||
var t_spawn_points: Array[Marker3D] = []
|
||||
var ct_spawn_points: Array[Marker3D] = []
|
||||
var team_manager: TeamManager
|
||||
var round_manager: RoundManager
|
||||
var economy_manager: EconomyManager
|
||||
var bomb: Bomb
|
||||
var avatars: Dictionary = {}
|
||||
|
||||
func _ready():
|
||||
team_manager = $"../TeamManager"
|
||||
round_manager = $"../RoundManager"
|
||||
economy_manager = $"../EconomyManager"
|
||||
bomb = get_tree().current_scene.get_node_or_null("Network/Bomb")
|
||||
|
||||
# Discover spawn points from scene containers
|
||||
var scene_root := get_tree().current_scene
|
||||
var t_container := scene_root.get_node_or_null("T Spawns")
|
||||
if t_container:
|
||||
for child in t_container.get_children():
|
||||
if child is Marker3D:
|
||||
t_spawn_points.append(child)
|
||||
var ct_container := scene_root.get_node_or_null("CT Spawns")
|
||||
if ct_container:
|
||||
for child in ct_container.get_children():
|
||||
if child is Marker3D:
|
||||
ct_spawn_points.append(child)
|
||||
|
||||
NetworkEvents.on_client_start.connect(_handle_connected)
|
||||
NetworkEvents.on_server_start.connect(_handle_host)
|
||||
NetworkEvents.on_peer_join.connect(_handle_new_peer)
|
||||
NetworkEvents.on_peer_leave.connect(_handle_leave)
|
||||
NetworkEvents.on_client_stop.connect(_handle_stop)
|
||||
NetworkEvents.on_server_stop.connect(_handle_stop)
|
||||
|
||||
if round_manager:
|
||||
round_manager.round_started.connect(_handle_round_start)
|
||||
|
||||
func _handle_connected(id: int):
|
||||
_spawn(id)
|
||||
|
||||
func _handle_host():
|
||||
_spawn(1)
|
||||
# Auto-start match
|
||||
if round_manager:
|
||||
round_manager.start_match()
|
||||
|
||||
func _handle_new_peer(id: int):
|
||||
_spawn(id)
|
||||
if multiplayer.is_server():
|
||||
team_manager.sync_to_peer(id)
|
||||
if economy_manager:
|
||||
economy_manager.sync_to_peer(id)
|
||||
if bomb:
|
||||
bomb.sync_to_peer(id)
|
||||
if avatars.size() <= 2:
|
||||
# First opponent joined — restart round so both spawn alive
|
||||
round_manager.start_round()
|
||||
else:
|
||||
# 3+ players: sync state, spectate until next round
|
||||
round_manager.sync_to_peer(id)
|
||||
var avatar = avatars.get(id)
|
||||
if avatar and avatar.has_method("set_spectator"):
|
||||
avatar.call_deferred("set_spectator")
|
||||
|
||||
func _handle_leave(id: int):
|
||||
if not avatars.has(id):
|
||||
return
|
||||
|
||||
var avatar = avatars[id] as Node
|
||||
avatar.queue_free()
|
||||
avatars.erase(id)
|
||||
if economy_manager:
|
||||
economy_manager.remove_player(id)
|
||||
|
||||
func _handle_stop():
|
||||
for avatar in avatars.values():
|
||||
avatar.queue_free()
|
||||
avatars.clear()
|
||||
|
||||
func _handle_round_start(_round_number: int):
|
||||
if not multiplayer.is_server():
|
||||
return
|
||||
# Respawn all players at their team spawn points
|
||||
for peer_id in avatars:
|
||||
var avatar = avatars[peer_id] as CharacterBody3D
|
||||
if avatar == null:
|
||||
continue
|
||||
# Fresh spawns (_has_played_round=false) always get default loadout
|
||||
var survived: bool = not avatar.is_dead and avatar._has_played_round
|
||||
var spawn_pos := get_team_spawn_point(peer_id)
|
||||
avatar.round_respawn(spawn_pos, survived)
|
||||
|
||||
# Assign bomb to a random T player
|
||||
if bomb:
|
||||
var t_members := team_manager.get_team_members(TeamManager.Team.T)
|
||||
if not t_members.is_empty():
|
||||
var idx := hash(_round_number * 31) % t_members.size()
|
||||
bomb.assign_to_player(t_members[idx])
|
||||
|
||||
func _spawn(id: int):
|
||||
# Auto-assign team on server
|
||||
if multiplayer.is_server():
|
||||
team_manager.auto_assign(id)
|
||||
|
||||
var avatar = player_scene.instantiate() as Node
|
||||
avatars[id] = avatar
|
||||
avatar.name += " #%d" % id
|
||||
add_child(avatar)
|
||||
avatar.global_position = get_team_spawn_point(id)
|
||||
|
||||
# Avatar is always owned by server
|
||||
avatar.set_multiplayer_authority(1)
|
||||
|
||||
# Pass references
|
||||
if avatar.has_method("setup_references"):
|
||||
avatar.setup_references(team_manager, round_manager)
|
||||
|
||||
# Init economy
|
||||
if multiplayer.is_server() and economy_manager:
|
||||
economy_manager.init_player(id)
|
||||
|
||||
print("Spawned avatar %s at %s" % [avatar.name, multiplayer.get_unique_id()])
|
||||
|
||||
# Avatar's input object is owned by player
|
||||
var input = avatar.find_child("Input")
|
||||
if input != null:
|
||||
input.set_multiplayer_authority(id)
|
||||
print("Set input(%s) ownership to %s" % [input.name, id])
|
||||
|
||||
# Notify RollbackSynchronizer about ownership changes
|
||||
var rollback_sync = avatar.find_child("RollbackSynchronizer")
|
||||
if rollback_sync and rollback_sync.has_method("process_settings"):
|
||||
rollback_sync.process_settings()
|
||||
|
||||
func get_team_spawn_point(peer_id: int, spawn_idx: int = 0) -> Vector3:
|
||||
var team := team_manager.get_team(peer_id)
|
||||
var points: Array[Marker3D]
|
||||
|
||||
match team:
|
||||
TeamManager.Team.T:
|
||||
points = t_spawn_points
|
||||
TeamManager.Team.CT:
|
||||
points = ct_spawn_points
|
||||
_:
|
||||
# Fallback: use T spawns
|
||||
points = t_spawn_points
|
||||
|
||||
if points.is_empty():
|
||||
return Vector3.ZERO
|
||||
|
||||
var idx := hash(peer_id * 37 + spawn_idx * 19) % points.size()
|
||||
return points[idx].global_position
|
||||
|
||||
func get_next_spawn_point(peer_id: int, spawn_idx: int = 0) -> Vector3:
|
||||
return get_team_spawn_point(peer_id, spawn_idx)
|
||||
Reference in New Issue
Block a user