online multiplayer chess game (note server currently down)
Diffstat (limited to 'networking/PacketHandler.gd')
| -rw-r--r-- | networking/PacketHandler.gd | 302 |
1 files changed, 302 insertions, 0 deletions
diff --git a/networking/PacketHandler.gd b/networking/PacketHandler.gd new file mode 100644 index 0000000..3351b85 --- /dev/null +++ b/networking/PacketHandler.gd @@ -0,0 +1,302 @@ +extends Network + +var lobby: Lobby = null +var reconnecting = false +signal hosting(newhosting) +signal signal_recieved(what) +signal chat(text) +signal undo(undo) +signal info_recieved(info) +signal start_game +signal move_data(data) +signal load_pgn(pgn) +signal request_result(what) # join/host true accepted, false rejected +signal rematch_result(what) +## for accounts(mostly) +signal signinresult(what) +signal signupresult(what) + +const DEFAULT_ERROR_MESSAGE = "Error. Please report this issue to bendn." +const HEADERS := { + "joinrequest": "J", + "hostrequest": "H", + "signup": "C", + "signin": ">", + "relay": "R", # relay goes to both + "signal": "S", # signal is one way + "loadpgn": "L", # server telling me to load a pgn + "info": "I", + "move": "M", + "undo": "<", + "rematch": "r", + "spectate": "0" # its a eye you see +} + +var game_code := "" + +const RELAYHEADERS := {chat = "C"} +const SIGNALHEADERS := {takeback = "T", draw = "D", resign = "R", info = "I"} # subheaders for HEADERS.signal + +var hosting := false setget set_hosting +var leaving := false +var lock_lobby_status := false + + +func set_lobby_status(status: String, isok: bool) -> void: + if !lock_lobby_status: + lobby.set_status(status, isok) + + +func set_hosting(newhosting: bool) -> void: + hosting = newhosting + emit_signal("hosting", newhosting) + + +func return() -> void: # return to the void + if hosting: + leaving = true + set_hosting(false) + + +func _ready() -> void: + Events.connect("go_back", self, "go_back") + if Utils.internet and get_tree().get_root().has_node("StartMenu"): + yield(get_tree().create_timer(.1), "timeout") + open_connection() + set_lobby_status("Connecting", true) + connect("load_pgn", self, "load_pgn") + + +func load_pgn(pgn: String) -> void: + if !Globals.grid: + _start_game() + yield(get_tree(), "idle_frame") + Globals.grid.load_pgn(pgn) # call deferred wont work since grid obj may be null + + +func _data_recieved() -> void: + var data = ws.get_peer(1).get_var() + Log.net(["RECIEVED:", data]) + var header: String = data.header + var text = data.data + match header: + HEADERS.undo: + emit_signal("undo", text) + HEADERS.move: + if !OS.is_window_focused() and !Debug.debug: # dont be annoying in debug mode + OS.request_attention() + emit_signal("move_data", text.move) + HEADERS.hostrequest: + host_result(text) + HEADERS.relay: + var relay: Dictionary = text + match relay.type: + RELAYHEADERS.chat: + emit_signal("chat", relay) + HEADERS.joinrequest: + join_result(text) + HEADERS.info: + yield(get_tree().create_timer(.5), "timeout") + emit_signal("info_recieved", text) + HEADERS.spectate: + spectate_result(text) + HEADERS.loadpgn: + emit_signal("load_pgn", text) + HEADERS.signal: + var signal: Dictionary = text + match signal.type: + _: + emit_signal("signal_recieved", signal) + HEADERS.signup: + emit_signal("signupresult", text) + HEADERS.signin: + emit_signal("signinresult", text) + HEADERS.rematch: + emit_signal("rematch_result", text) + _: + Log.err("unknown header %s" % header) + + +func _connection_established(protocol) -> void: + ._connection_established(protocol) + + +func _connection_closed(_was_clean_closed) -> void: + ._connection_closed(_was_clean_closed) + var err = yield(rejoin(), "completed") + if err: + go_back("Connection closed, please check your internet, and reload the game.", false) + + +func _connection_error() -> void: + ._connection_error() + var err = yield(rejoin(), "completed") + if err: + go_back("Connection error, please check your internet, and reload the game.", false) + + +const join_err_table := { + "FULL": "This game is full. Double check your spelling, or host your own game..", + "NOT_EXIST": "This game does not exist. Double check your spelling, or host it.", + "NO_GAMECODE": "Your game name is empty.", + "NO_ID": "Your id is undefined. Please report this issue to bendn." +} + + +func join_result(accepted) -> void: + handle_result(accepted, "Joined!", join_err_table) + + +const host_err_table := { + "ALREADY_EXISTS": "This game name is taken. Pick a new name.", # game is full + "ALREADY_EXISTS_EMPTY": "This game name is taken. Pick a new name, or join.", # someone else hosted, but noone is joining them :( + "NO_GAMECODE": "Your game name is empty.", + "NO_ID": "Your id is undefined. Please report this issue to bendn." +} + + +func host_result(accepted) -> void: + set_hosting(handle_result(accepted, "Hosted!", host_err_table)) + + +const spectate_err_table := {"NOT_EXIST": "This game does not exist. Double check your spelling, or host it."} + + +func spectate_result(accepted) -> void: + if handle_result(accepted, "Watching", spectate_err_table, true): + Globals.spectating = true + _start_game() + yield(get_tree().create_timer(.5), "timeout") + Globals.grid.load_pgn(accepted.pgn) + emit_signal("info_recieved", accepted) + + +func handle_result(accepted, resultstring: String, err_table: Dictionary, quick_return := false) -> bool: + var err = accepted.get("err", false) + emit_signal("request_result", false if err else true) + if !err: + if quick_return: + return true + Globals.team = "w" if accepted.idx == 0 else "b" + Log.debug("Team set to " + Utils.expand_color(Globals.team)) + set_lobby_status(resultstring, true) + return true + set_lobby_status(err_table.get(err, "Error. Please report this issue to bendn."), false) + lobby.set_buttons(true) + return false + + +func go_back(error: String, isok: bool) -> void: + Globals.reset_vars() + if Globals.playing: + $"/root/Game".queue_free() + set_lobby_status(error, isok) + lobby.toggle(true) + lobby.focus() + lobby.set_buttons(true) + + +func _start_game() -> void: + set_hosting(false) + Log.debug("Created board") + var ui: GameUI = load("res://ui/board/Game.tscn").instance() + var b: Grid = ui.get_board() as Grid + b.team = Globals.team + Log.debug("Set board team to %s" % Utils.expand_color(b.team)) + get_tree().get_root().add_child(ui) + b.spectating = Globals.spectating + lobby.toggle(false) + emit_signal("start_game") + lobby.set_buttons(false) + SoundFx.play("Victory") + + yield(get_tree(), "idle_frame") + b.auto_flip() + + +func rejoin(tries := 5, interval := 2) -> int: # on disconnect, try to rejoin + var err = yield(open_connection(tries, interval), "completed") + if reconnecting == true: + return ERR_ALREADY_IN_USE + reconnecting = true + Log.info("reconnecting...") + if not err: + if Globals.playing: + Log.info("reconnecting(rejoining)...") + lock_lobby_status = true + var rejoined = false + for try in tries + 1: + join_game() + var result = yield(self, "request_result") + if result: + disconnect("load_pgn", self, "load_pgn") + var pgn = yield(self, "load_pgn") + if Globals.grid.chess.pgn() != pgn: + Log.info("attempting to load %s" % pgn) + Globals.grid.load_pgn(pgn) + connect("load_pgn", self, "load_pgn") + Log.info("reconnected(rejoined)!") + rejoined = true + break + lock_lobby_status = false + if rejoined == false: + Log.err("reconnect failed(rejoin)!") + reconnecting = false + return ERR_INVALID_DATA + else: # not playing: just reconnect + Log.info("reconnected!") + else: + Log.err("reconnect failed!") + reconnecting = false + return err + + +## packet sending wrapper functions +func signin(data): + send_packet(data, HEADERS.signin) + + +func signup(data): + send_packet(data, HEADERS.signup) + + +func signal(body: Dictionary, header: String, _mainheader := HEADERS.signal) -> Dictionary: + var data: Dictionary = Utils.append_dict({"type": header}, body) + send_packet(data, _mainheader) + return data + + +func join_game(game: String = game_code) -> void: + send_gamecode_packet(Creds.get_public(), HEADERS.joinrequest, game) + + +func host_game(game: String = game_code, white := true, moves_array: PoolStringArray = []) -> void: + var pckt = Utils.append_dict(Creds.get_public(), {team = white, moves = moves_array}) + send_gamecode_packet(pckt, HEADERS.hostrequest, game) + + +func spectate(game: String = game_code) -> void: + send_gamecode_packet(Creds.get_public(), HEADERS.spectate, game) + + +func send_gamecode_packet(data: Dictionary, header: String, gamecode: String = game_code): + send_packet(Utils.append_dict({"gamecode": gamecode}, data), header) + + +func relay_signal(body: Dictionary, header: String) -> Dictionary: # its really the same thing as signal() + return signal(body, header, HEADERS.relay) + + +func send_mov(mov: String) -> void: + send_packet({move = mov}, HEADERS.move) + + +static func construct_errstr(packet, err_table, default_message := DEFAULT_ERROR_MESSAGE) -> String: + var errstr: String = "" + var err: String = packet.get("err", false) + if err: + errstr = err_table.get(err, default_message) + var stack = packet.get("stack", false) + if stack: + errstr += " (Stack trace: %s) " % stack + return errstr |