# Hurry Curry! - 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 packet(packet: Dictionary) signal connection_closed() const VERSION_MAJOR: int = 7 const VERSION_MINOR: int = 0 var connected := false var socket := WebSocketPeer.new() func _ready(): print("Multiplayer connect"); socket.connect_to_url(Global.server_url) socket.inbound_buffer_size = 1024 * 1024 * 4 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() if code == socket.STATE_CLOSED else tr("c.error.websocket.unavailable") connection_closed.emit(tr("c.error.websocket").format([code, reason, code != -1])) self.queue_free() func fix_packet_types(val): match typeof(val): TYPE_FLOAT: return val TYPE_STRING: return val TYPE_BOOL: return val TYPE_ARRAY: return val.map(fix_packet_types) TYPE_DICTIONARY: var newval = {} for k in val.keys(): if typeof(val[k]) == TYPE_ARRAY and val[k].size() == 2 and typeof(val[k][0]) == TYPE_FLOAT and typeof(val[k][1]) == TYPE_FLOAT: if k in ["tile"]: newval[k] = Vector2i(val[k][0], val[k][1]) elif k in ["pos", "position"]: newval[k] = Vector2(val[k][0], val[k][1]) # TODO reenable when fixed # elif k in ["player", "id"] and typeof(val[k]) == TYPE_FLOAT: # newval[k] = int(val[k]) else: newval[k] = fix_packet_types(val[k]) return newval func handle_packet(coded): var p = decode_packet(coded) if p == null: return p = fix_packet_types(p) match p["type"]: "version": var major = p["major"] var minor = p["minor"] if major != VERSION_MAJOR and minor >= VERSION_MINOR: socket.close() connected = false connection_closed.emit(tr("c.error.version_mismatch").format([major, minor, VERSION_MAJOR, VERSION_MINOR])) _: packet.emit(p) func send_join(player_name: String, character: int): send_packet({ "type": "join", "name": player_name, "character": character }) func send_movement(player: int, pos: Vector2, direction: Vector2, boost: bool): send_packet({ "type": "movement", "player": player, "pos": [pos.x, pos.y], "dir": [direction.x, direction.y], "boost": boost }) func send_tile_interact(player: int, pos: Vector2i, edge: bool): @warning_ignore("incompatible_ternary") send_packet({ "type": "interact", "player": player, "pos": [pos.x, pos.y] if edge else null, }) func send_player_interact(_player: int, _edge: bool): push_error("not yet implemented") func send_chat(player: int, message: String): send_packet({ "type": "communicate", "player": player, "persist": false, "message": { "text": message } }) func send_replay_tick(dt: float): send_packet({ "type": "replay_tick", "dt": dt }) func send_leave(player: int): send_packet({ "type": "leave", "player": player, }) func send_packet(p): var json = JSON.stringify(p) 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