extends Node ## Canlı multiplayer istemcisi — diğer oyuncuların pozisyonlarını WebSocket ile alır. ## Sunucu eventleri: online_list (sadece bağlanınca), player_joined, player_moved, ## player_left, pong. Kopunca 5 sn'de bir yeniden bağlanır. signal player_joined(p: Dictionary) signal player_moved(p: Dictionary) signal player_left(char_id: int) signal online_list(players: Array) signal chat_received(msg: Dictionary) signal chat_history(messages: Array) # global geçmiş (bağlanınca) signal local_chat_history(messages: Array) # bulunulan lokasyonun geçmişi (istek üzerine) signal connection_changed(is_connected: bool) const PING_INTERVAL := 25.0 # sunucu presence TTL 120sn, ping ile yenilenir const RECONNECT_DELAY := 5.0 var _ws: WebSocketPeer = null var _open := false var _ping_accum := 0.0 var _reconnect_accum := 0.0 var _should_run := false func start() -> void: if _should_run: return _should_run = true _connect_ws() func stop() -> void: _should_run = false if _ws != null: _ws.close() _ws = null if _open: _open = false connection_changed.emit(false) func _connect_ws() -> void: if Auth.token == "": return var url := Config.get_api_base().replace("https://", "wss://").replace("http://", "ws://") + "/ws?token=" + Auth.token _ws = WebSocketPeer.new() var err := _ws.connect_to_url(url) if err != OK: push_warning("WS bağlantı hatası: %d" % err) _ws = null _ping_accum = 0.0 func _process(delta: float) -> void: if not _should_run: return if _ws == null: _reconnect_accum += delta if _reconnect_accum >= RECONNECT_DELAY: _reconnect_accum = 0.0 _connect_ws() return _ws.poll() var state := _ws.get_ready_state() if state == WebSocketPeer.STATE_OPEN: if not _open: _open = true connection_changed.emit(true) while _ws.get_available_packet_count() > 0: _handle_packet(_ws.get_packet().get_string_from_utf8()) _ping_accum += delta if _ping_accum >= PING_INTERVAL: _ping_accum = 0.0 _ws.send_text("{\"type\":\"ping\"}") elif state == WebSocketPeer.STATE_CLOSED: _ws = null _reconnect_accum = 0.0 if _open: _open = false connection_changed.emit(false) func _handle_packet(text: String) -> void: var json := JSON.new() if json.parse(text) != OK: return if not (json.data is Dictionary): return var msg: Dictionary = json.data match str(msg.get("type", "")): "online_list": var players: Array = msg.get("players", []) online_list.emit(players) "player_joined": player_joined.emit(msg) "player_moved": player_moved.emit(msg) "player_left": player_left.emit(int(msg.get("char_id", 0))) "chat": chat_received.emit(msg) "chat_history": var messages: Array = msg.get("messages", []) if str(msg.get("channel", "global")) == "local": local_chat_history.emit(messages) else: chat_history.emit(messages) "pong": pass func send_chat(text: String, channel: String = "global") -> void: if _ws == null or _ws.get_ready_state() != WebSocketPeer.STATE_OPEN: return var trimmed := text.strip_edges() if trimmed == "": return _ws.send_text(JSON.stringify({"type": "chat", "text": trimmed, "channel": channel})) func request_local_history() -> void: if _ws == null or _ws.get_ready_state() != WebSocketPeer.STATE_OPEN: return _ws.send_text("{\"type\":\"get_local_history\"}")