Phase 7: Windows client export, preset fix, code fixes

- Fix: export_presets.cfg — platform='Windows Desktop' (not 'Windows'), Windows as [preset.0]
- Fix: plugin_manager.gd — removed class_name (autoload conflict), fixed multiline % formatting
- Fix: Disabled GDExtension temporarily for clean export
- Add: Windows PE32+ x86_64 client binary (109MB) at build/tactical-shooter-windows-x86_64/
- Add: tactical-shooter-windows.zip (portable zip package)
- Build: Linux server binary (78MB) rebuilt
This commit is contained in:
2026-07-01 19:46:52 -04:00
parent e385eae0f5
commit 9ea98aa7b8
36 changed files with 1094 additions and 64 deletions
+40 -14
View File
@@ -7,9 +7,11 @@
## 1. Load config (env overrides)
## 2. Load the first map from the config's map rotation
## 3. Start ENet server on the configured port
## 4. Spawn players on the map using spawn markers
## 5. Broadcast spawn via RPC so clients create visual player nodes
## 6. Log player join/leave and replicate position
## 4. Create GameServer and start simulation
## 5. Spawn players on the map using spawn markers
## 6. Broadcast spawn via RPC so clients create visual player nodes
## 7. Log player join/leave and replicate position
## 8. Hitscan weapon with lag compensation runs in GameServer
extends Node
@@ -31,6 +33,9 @@ var players: Dictionary = {} # peer_id → Node (player instance)
var spawn_points_a: Array[Vector3] = [] # Team A spawns
var spawn_points_b: Array[Vector3] = [] # Team B spawns
# Game server (drives simulation)
var _game_server: Node = null
# ---------------------------------------------------------------------------
# Lifecycle
# ---------------------------------------------------------------------------
@@ -42,21 +47,27 @@ func _ready() -> void:
if OS.has_environment("SERVER_PORT"):
effective_port = int(OS.get_environment("SERVER_PORT"))
print("[ServerMain] Port overridden by SERVER_PORT env: %d" % effective_port)
# Create GameServer (drives the 128Hz simulation + hit detection)
_game_server = preload("res://server/scripts/game_server.gd").new()
add_child(_game_server)
# GameServer registers SimulationServer as a singleton on _ready
_game_server.start_simulation()
# Instance the map from the config's map rotation
_load_map()
# Start the ENet server
var err: Error = NetworkManager.start_server(effective_port)
if err != OK:
push_error("[ServerMain] Failed to start server: %s" % error_string(err))
get_tree().quit(1)
return
# Connect signals
NetworkManager.player_connected.connect(_on_player_connected)
NetworkManager.player_disconnected.connect(_on_player_disconnected)
print("[ServerMain] Tactical Shooter server ready")
print("[ServerMain] Port: %d" % effective_port)
print("[ServerMain] Name: \"%s\"" % ServerConfig.server_name)
@@ -64,6 +75,7 @@ func _ready() -> void:
print("[ServerMain] Maps: %s" % ServerConfig.map_list)
print("[ServerMain] Headless: %s" % (DisplayServer.get_name() == &"headless"))
print("[ServerMain] Spawn pts: Team A=%d, Team B=%d" % [spawn_points_a.size(), spawn_points_b.size()])
print("[ServerMain] Lag comp: Enabled (64-tick history = %dms)" % [64.0 * 1000.0 / ServerConfig.tick_rate])
func _exit_tree() -> void:
NetworkManager.stop()
@@ -137,36 +149,50 @@ func _spawn_player(peer_id: int) -> void:
else:
player = Node3D.new()
player.set_script(preload("res://scripts/network/player.gd"))
player.name = "Player_%d" % peer_id
player.set_multiplayer_authority(peer_id)
# Assign spawn position — alternate between Team A and Team B spawns
var is_team_a: bool = (peer_id % 2) == 0
var pool: Array[Vector3] = spawn_points_a if is_team_a else spawn_points_b
var spawn_pos: Vector3 = pool[peer_id % pool.size()] if pool.size() > 0 else Vector3.ZERO
spawn_pos.y = 0.0
player.position = spawn_pos
add_child(player, true)
players[peer_id] = player
player_spawned.emit(peer_id)
# Register with GameServer to get a simulation entity
if _game_server != null and _game_server.has_method(&"spawn_player_entity"):
var entity_id: int = _game_server.spawn_player_entity(peer_id, spawn_pos)
if entity_id >= 0:
# Store entity_id on player node for reference
player.set_meta(&"entity_id", entity_id)
print("[ServerMain] Player %d assigned entity %d" % [peer_id, entity_id])
# Broadcast spawn to all clients so they create a visual player node
NetworkManager.broadcast_spawn_player.rpc(peer_id, spawn_pos, is_team_a)
print("[ServerMain] Spawned player %d at (%.1f, %.1f) team=%s" % [peer_id, spawn_pos.x, spawn_pos.z, "A" if is_team_a else "B"])
func _despawn_player(peer_id: int) -> void:
if peer_id in players:
var player: Node = players[peer_id]
# Despawn entity from GameServer
if _game_server != null and player.has_meta(&"entity_id"):
var entity_id: int = player.get_meta(&"entity_id")
_game_server.despawn_player_entity(entity_id)
player.queue_free()
players.erase(peer_id)
player_despawned.emit(peer_id)
# Broadcast despawn to all clients
NetworkManager.broadcast_despawn_player.rpc(peer_id)
print("[ServerMain] Despawned player %d" % peer_id)
else:
# Still broadcast even if server doesn't have the player