extends Node signal connected signal disconnected signal login_ok(my_id, players) signal player_joined(id, name, x, y, skin_id) signal player_left(id) signal player_moved(id, x, y, flip_h, frame) signal chat_message_received(sender_id, text) signal private_chat_message_received(sender_id, text) signal notices_received(data) const DEFAULT_SERVER_URL = "ws://127.0.0.1:8910" var socket := WebSocketPeer.new() var my_id: int = -1 var player_info := {"name": "Player", "skin_id": 0} var players := {} # id -> {name, x, y, skin_id} var _connected := false var next_spawn_position: Vector2 = Vector2.ZERO func _ready(): process_mode = Node.PROCESS_MODE_ALWAYS func _process(_delta): socket.poll() var state = socket.get_ready_state() if state == WebSocketPeer.STATE_OPEN: if not _connected: _connected = true _on_connected() while socket.get_available_packet_count(): var packet = socket.get_packet() _on_message(packet.get_string_from_utf8()) elif state == WebSocketPeer.STATE_CLOSED: if _connected: _connected = false _on_disconnected() func connect_to_server(url: String = ""): if url.is_empty(): url = DEFAULT_SERVER_URL var err = socket.connect_to_url(url) if err != OK: print("Failed to connect: ", err) return err func _on_connected(): print("Connected to server!") # Send login send_json({ "type": "login", "name": player_info["name"], "skin_id": player_info["skin_id"] }) connected.emit() func _on_disconnected(): print("Disconnected from server") my_id = -1 players.clear() disconnected.emit() func _on_message(data: String): var json = JSON.new() var err = json.parse(data) if err != OK: print("JSON parse error: ", data) return var msg = json.data # if msg.get("type") == "player_move" and int(msg.get("id", -1)) != my_id: # print("RAW NET: ", data) var msg_type = msg.get("type", "") match msg_type: "login_ok": my_id = int(msg["id"]) for p in msg["players"]: players[int(p["id"])] = { "name": p["name"], "x": p["x"], "y": p["y"], "skin_id": p["skin_id"] } login_ok.emit(my_id, players) "player_join": var id = int(msg["id"]) players[id] = { "name": msg["name"], "x": msg["x"], "y": msg["y"], "skin_id": msg["skin_id"] } player_joined.emit(id, msg["name"], msg["x"], msg["y"], msg["skin_id"]) "player_leave": var id = int(msg["id"]) players.erase(id) player_left.emit(id) "player_move": var id = int(msg["id"]) if id != my_id: # Only log others to reduce spam, or log all? Let's log all for now if issue is critical print("Net: Recv Move for ID: ", id, " Frame: ", msg.get("frame", 1)) if players.has(id): players[id]["x"] = msg["x"] players[id]["y"] = msg["y"] player_moved.emit(id, msg["x"], msg["y"], msg.get("flip_h", false), int(msg.get("frame", 1))) "chat": var id = int(msg["id"]) chat_message_received.emit(id, msg["text"]) "private_chat": var id = int(msg["sender_id"]) private_chat_message_received.emit(id, msg["text"]) "notices_list": notices_received.emit(msg.get("data", [])) "scene_peers": # Refresh player list for new scene players.clear() for p in msg["players"]: players[int(p["id"])] = { "name": p["name"], "x": p["x"], "y": p["y"], "skin_id": p["skin_id"] } # Emit login_ok to trigger refresh (or we could add a scene_loaded signal) login_ok.emit(my_id, players) func send_json(obj: Dictionary): if socket.get_ready_state() == WebSocketPeer.STATE_OPEN: socket.send_text(JSON.stringify(obj)) func send_move(x: float, y: float, flip_h: bool, frame: int): send_json({"type": "move", "x": x, "y": y, "flip_h": flip_h, "frame": frame}) func send_change_scene(scene_name: String, pos: Vector2 = Vector2(-20000, -20000)): var data = {"type": "change_scene", "scene": scene_name} if pos.x != -20000: data["x"] = pos.x data["y"] = pos.y send_json(data) func send_message(text: String): send_json({"type": "chat", "text": text}) func send_private_message(target_id: int, text: String): send_json({"type": "private_chat", "target_id": target_id, "text": text}) func request_notices(): send_json({"type": "get_notices"}) func set_player_name(n: String): player_info["name"] = n func set_skin_id(id: int): player_info["skin_id"] = id