# Undercooked - a game about cooking # Copyright 2024 nokoe # Copyright 2024 metamuffin # Copyright 2024 tpart # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, version 3 of the License only. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # class_name Multiplayer extends Node signal init(player_id: int) signal data( item_names: Array, tile_names: Array, tile_collide: Array, tile_interact: Array, map_names: Array ) signal set_tile(tile: Vector2i, kind: int, neighbors: Array) signal remove_tile(tile: Vector2i) signal clear_message(player: int) signal text_message(player: int, text: String, persist: bool) signal item_message(player: int, item: int, persist: bool) signal effect_message(player: int, effect: String, persist: bool) signal add_player(player: int, name: String, pos: Vector2, character: int) signal remove_player(player: int) signal position(player: int, position: Vector2, rotation: float, boosting: bool) signal take_item(tile: Vector2i, player: int) signal put_item(player: int, tile: Vector2i, ) signal pass_item_player(from: int, to: int) signal pass_item_tile(from: Vector2i, to: Vector2i) signal set_tile_item(tile: Vector2i, item: int) signal remove_tile_item(tile: Vector2i) signal set_player_item(player: int, item: int) signal remove_player_item(player: int) signal set_tile_progress(tile: Vector2i, progress: float, warn: bool) signal set_player_progress(player: int, progress: float, warn: bool) signal set_tile_finished(tile: Vector2i, warn: bool) signal set_player_finished(player: int, warn: bool) signal set_ingame(state: bool, lobby: bool) signal score(demands_failed: int, demands_completed: int, points: int, time_remaining: float) signal hide_score() signal server_message(text: String) signal connection_closed(reason: String) var connected := false var socket := WebSocketPeer.new() func _ready(): print("Multiplayer connect"); socket.connect_to_url(Global.server_url) connected = true func _notification(what): if what == NOTIFICATION_PREDELETE: print("Multiplayer disconnect"); socket.close() connected = false func _process(_delta): if connected: socket.poll() var state = socket.get_ready_state() if state == WebSocketPeer.STATE_OPEN: while socket.get_available_packet_count(): handle_packet(socket.get_packet()) elif state == WebSocketPeer.STATE_CLOSED: var code = socket.get_close_code() var reason = socket.get_close_reason() connection_closed.emit( "WebSocket closed with code: %d, reason %s. Clean: %s" % [code, reason, code != -1] ) self.queue_free() func handle_packet(bytes: PackedByteArray): var decoded = decode_packet(bytes) if decoded == null: return var packet_type: String = decoded["type"] match packet_type: "init": var player_id = decoded["id"] init.emit(player_id) "data": var item_names = decoded["data"]["item_names"] var tile_names = decoded["data"]["tile_names"] var tile_collide = decoded["data"]["tile_collide"] var tile_interact = decoded["data"]["tile_interact"] var map_names = decoded["data"]["map_names"] data.emit(item_names, tile_names, tile_collide, tile_interact, map_names) "add_player": var id = decoded["id"] var player_name = decoded["name"] var pos = decoded["position"] var character = decoded["character"] add_player.emit(id, player_name, pos_to_vec2(pos), character) "remove_player": var id = decoded["id"] remove_player.emit(id) "position": var player = decoded["player"] var pos = decoded["pos"] var rot = decoded["rot"] var boosting = decoded["boosting"] position.emit(player, pos_to_vec2(pos), rot, boosting) "take_item": push_warning("take_item is deprecated") var tile = pos_to_vec2i(decoded["tile"]) var player = decoded["player"] take_item.emit(tile, player) "put_item": push_warning("put_item is deprecated") var tile = pos_to_vec2i(decoded["tile"]) var player = decoded["player"] put_item.emit(tile, player) "move_item": var from: Dictionary = decoded["from"] var to: Dictionary = decoded["to"] var from_player = from.get("player") var from_tile = from.get("tile") var to_player = to.get("player") var to_tile = to.get("tile") if from_player != null and to_player != null: pass_item_player.emit(from_player, to_player) elif from_tile != null and to_player != null: take_item.emit(pos_to_vec2i(from_tile), to_player) elif from_player != null and to_tile != null: put_item.emit(from_player, pos_to_vec2i(to_tile)) elif from_tile != null and to_tile != null: pass_item_tile.emit(pos_to_vec2i(from_tile), pos_to_vec2i(to_tile)) "set_active": push_warning("set_active is deprecated") var tile = pos_to_vec2i(decoded["tile"]) var warn = decoded["warn"] var progress = decoded.get("progress") if progress != null: set_tile_progress.emit(tile, progress, warn) else: set_tile_finished.emit(tile, warn) "set_progress": var item: Dictionary = decoded["item"] var tile = item.get("tile") var player = item.get("player") var warn = decoded["warn"] var progress = decoded.get("progress") if progress != null: if tile != null: set_tile_progress.emit(pos_to_vec2i(tile), progress, warn) else: set_player_progress.emit(player, progress, warn) else: if tile != null: set_tile_finished.emit(pos_to_vec2i(tile), warn) else: set_player_finished.emit(player, warn) "set_tile_item": push_warning("set_tile_item is deprecated") var tile = pos_to_vec2i(decoded["tile"]) var item = decoded.get("item") if item != null: set_tile_item.emit(tile, item) else: remove_tile_item.emit(tile) "set_player_item": push_warning("set_player_item is deprecated") var player = decoded["player"] var item = decoded.get("item") if item != null: set_player_item.emit(player, item) else: remove_player_item.emit(player) "set_item": var location: Dictionary = decoded["location"] var tile = location.get("tile") var player = location.get("player") var item = decoded.get("item") if item != null: if tile != null: set_tile_item.emit(pos_to_vec2i(tile), item) else: set_player_item.emit(player, item) else: if tile != null: remove_tile_item.emit(pos_to_vec2i(tile)) else: remove_player_item.emit(player) "update_map": var tile: Vector2i = pos_to_vec2i(decoded["tile"]) var kind = decoded.get("kind") var neighbors: Array = decoded["neighbors"] if kind != null: set_tile.emit(tile, kind, neighbors) else: remove_tile.emit(tile) "communicate": var player: int = decoded["player"] var message = decoded.get("message") var persist: bool = decoded["persist"] if message != null: var item = message.get("item") var text = message.get("text") var effect = message.get("effect") if item != null: item_message.emit(player, item, persist) elif text != null: text_message.emit(player, text, persist) elif effect != null: effect_message.emit(player, effect, persist) else: push_error("neither text, item nor effect provided") else: clear_message.emit(player) "set_ingame": var state = decoded["state"] var lobby = decoded["lobby"] set_ingame.emit(state, lobby) "error": var message = decoded["message"] push_warning("server error: %s" % message) "score": var demands_failed: int = decoded["demands_failed"] var demands_completed: int = decoded["demands_completed"] var points: int = decoded["points"] var time_remaining = decoded.get("time_remaining") if time_remaining != null: score.emit(demands_failed, demands_completed, points, time_remaining) else: hide_score.emit() "server_message": var text = decoded["text"] server_message.emit(text) _: push_error("Unrecognized packet type: %s" % packet_type) func send_join(player_name: String, character: int): send_packet({ "type": "join", "name": player_name, "character": character }) func send_position(pos: Vector2, rotation: float, boosting: bool): send_packet({ "type": "position", "pos": [pos.x, pos.y], "rot": rotation, "boosting": boosting }) func send_tile_interact(pos: Vector2i, edge: bool): send_packet({ "type": "interact", "pos": [pos.x, pos.y], "edge": edge }) func set_player_interact(_player: int, _edge: bool): push_error("not yet implemented") func send_chat(message: String): send_packet({ "type": "communicate", "persist": false, "message": { "text": message } }) func send_packet(packet): var json = JSON.stringify(packet) socket.send_text(json) func decode_packet(bytes: PackedByteArray): var json = JSON.new() var in_str = bytes.get_string_from_utf8() var error = json.parse(in_str) if error == OK: return json.data else: print("Decode of packet failed: %s in %s" % [json.get_error_message(), in_str]) return null func pos_to_vec2(pos: Array) -> Vector2: return Vector2(pos[0], pos[1]) func pos_to_vec2i(pos: Array) -> Vector2i: return Vector2i(pos[0], pos[1])