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,156 @@
|
||||
@tool
|
||||
extends Node
|
||||
class_name MCPClient
|
||||
## WebSocket client for communication with the MCP server.
|
||||
## Handles connection, reconnection, and message routing.
|
||||
|
||||
signal connected
|
||||
signal disconnected
|
||||
signal tool_requested(request_id: String, tool_name: String, args: Dictionary)
|
||||
|
||||
const DEFAULT_URL := "ws://127.0.0.1:6505"
|
||||
const RECONNECT_DELAY := 3.0
|
||||
const MAX_RECONNECT_DELAY := 30.0
|
||||
const MAX_PACKETS_PER_FRAME := 32
|
||||
|
||||
var socket: WebSocketPeer = WebSocketPeer.new()
|
||||
var server_url: String = DEFAULT_URL
|
||||
var _is_connected := false
|
||||
var _reconnect_timer: Timer
|
||||
var _current_reconnect_delay := RECONNECT_DELAY
|
||||
var _should_reconnect := true
|
||||
var _project_path: String
|
||||
var _initialized := false
|
||||
|
||||
func _ready() -> void:
|
||||
_project_path = ProjectSettings.globalize_path("res://")
|
||||
|
||||
# Create reconnect timer
|
||||
_reconnect_timer = Timer.new()
|
||||
_reconnect_timer.one_shot = true
|
||||
_reconnect_timer.timeout.connect(_on_reconnect_timer)
|
||||
add_child(_reconnect_timer)
|
||||
_initialized = true
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
if not _initialized:
|
||||
return
|
||||
if socket.get_ready_state() == WebSocketPeer.STATE_CLOSED:
|
||||
if _is_connected:
|
||||
_handle_disconnect()
|
||||
return
|
||||
|
||||
socket.poll()
|
||||
|
||||
match socket.get_ready_state():
|
||||
WebSocketPeer.STATE_OPEN:
|
||||
if not _is_connected:
|
||||
_handle_connect()
|
||||
var packets_processed := 0
|
||||
while socket.get_available_packet_count() > 0 and packets_processed < MAX_PACKETS_PER_FRAME:
|
||||
_handle_message(socket.get_packet().get_string_from_utf8())
|
||||
packets_processed += 1
|
||||
|
||||
WebSocketPeer.STATE_CLOSING:
|
||||
pass # Wait for close
|
||||
|
||||
WebSocketPeer.STATE_CLOSED:
|
||||
if _is_connected:
|
||||
_handle_disconnect()
|
||||
|
||||
func connect_to_server(url: String = DEFAULT_URL) -> void:
|
||||
server_url = url
|
||||
_should_reconnect = true
|
||||
_current_reconnect_delay = RECONNECT_DELAY
|
||||
_attempt_connection()
|
||||
|
||||
func disconnect_from_server() -> void:
|
||||
_should_reconnect = false
|
||||
if _reconnect_timer:
|
||||
_reconnect_timer.stop()
|
||||
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
||||
socket.close()
|
||||
_is_connected = false
|
||||
|
||||
func _attempt_connection() -> void:
|
||||
if socket.get_ready_state() != WebSocketPeer.STATE_CLOSED:
|
||||
socket.close()
|
||||
|
||||
print("[MCP] Connecting to ", server_url, "...")
|
||||
var err := socket.connect_to_url(server_url)
|
||||
if err != OK:
|
||||
push_error("[MCP] Failed to connect: ", err)
|
||||
_schedule_reconnect()
|
||||
|
||||
func _handle_connect() -> void:
|
||||
_is_connected = true
|
||||
_current_reconnect_delay = RECONNECT_DELAY # Reset backoff
|
||||
print("[MCP] Connected to server")
|
||||
|
||||
# Send godot_ready message with project info
|
||||
_send_message({
|
||||
&"type": &"godot_ready",
|
||||
&"project_path": _project_path,
|
||||
})
|
||||
|
||||
connected.emit()
|
||||
|
||||
func _handle_disconnect() -> void:
|
||||
_is_connected = false
|
||||
print("[MCP] Disconnected from server")
|
||||
disconnected.emit()
|
||||
|
||||
if _should_reconnect:
|
||||
_schedule_reconnect()
|
||||
|
||||
func _schedule_reconnect() -> void:
|
||||
if not _reconnect_timer:
|
||||
return
|
||||
print("[MCP] Reconnecting in ", _current_reconnect_delay, " seconds...")
|
||||
_reconnect_timer.start(_current_reconnect_delay)
|
||||
# Exponential backoff
|
||||
_current_reconnect_delay = min(_current_reconnect_delay * 2, MAX_RECONNECT_DELAY)
|
||||
|
||||
func _on_reconnect_timer() -> void:
|
||||
_attempt_connection()
|
||||
|
||||
func _handle_message(json_string: String) -> void:
|
||||
var message = JSON.parse_string(json_string)
|
||||
if message == null:
|
||||
push_error("[MCP] Failed to parse message: ", json_string)
|
||||
return
|
||||
|
||||
var msg_type: String = message.get(&"type", "")
|
||||
match msg_type:
|
||||
"ping":
|
||||
_send_message({&"type": &"pong"})
|
||||
"tool_invoke":
|
||||
var request_id: String = message.get(&"id", "")
|
||||
var tool_name: String = message.get(&"tool", "")
|
||||
var args: Dictionary = message.get(&"args", {})
|
||||
print("[MCP] Tool request: ", tool_name, " (", request_id, ")")
|
||||
tool_requested.emit(request_id, tool_name, args)
|
||||
_:
|
||||
print("[MCP] Unknown message type: ", msg_type)
|
||||
|
||||
func send_tool_result(request_id: String, success: bool, result = null, error: String = "") -> void:
|
||||
var response := {
|
||||
&"type": &"tool_result",
|
||||
&"id": request_id,
|
||||
&"success": success,
|
||||
}
|
||||
|
||||
if success:
|
||||
response[&"result"] = result
|
||||
else:
|
||||
response[&"error"] = error
|
||||
|
||||
_send_message(response)
|
||||
print("[MCP] Sent result for ", request_id, " (success=", success, ")")
|
||||
|
||||
func _send_message(message: Dictionary) -> void:
|
||||
if socket.get_ready_state() == WebSocketPeer.STATE_OPEN:
|
||||
socket.send_text(JSON.stringify(message))
|
||||
|
||||
func is_connected_to_server() -> bool:
|
||||
return _is_connected
|
||||
Reference in New Issue
Block a user