From 6fe7ae8c495d043dfeabc21f372b6fe8faa0f82b Mon Sep 17 00:00:00 2001 From: Rem's Little Helper Date: Wed, 4 Feb 2026 14:52:59 +0000 Subject: [PATCH 1/3] feat: support reservation websocket messages --- scripts/Connection.cs | 101 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/scripts/Connection.cs b/scripts/Connection.cs index 65141a4..6392f21 100644 --- a/scripts/Connection.cs +++ b/scripts/Connection.cs @@ -42,6 +42,11 @@ public partial class Connection : Node { public event Action OnGetDataAcks; public event Action OnSetDataAcks; + // Reservation system (admin-only server feature) + public event Action OnReservationAddAck; + public event Action OnReservationDeleteAck; + public event Action> OnUpdatedReservations; + public event Action OnWsConnectionSuccess; public event Action OnWsConnectionFailed; public event Action OnWsDisconnect; @@ -53,6 +58,10 @@ public partial class Connection : Node { public bool IsPlayer { get; private set; } public TournamentType ActiveTournament { get; private set; } public bool DemoMode { get; private set; } + + // (player1, player2) tuples as reported by the server. + public List<(string player1, string player2)> Reservations { get; private set; } = []; + public List<(string, int)> PreviousMoves { get; private set; } = []; public double CurrentWaitTimeout { get; private set; } = 5.0; public double MaxTimeout { get; private set; } = 30.0; @@ -219,6 +228,36 @@ public partial class Connection : Node { sendCommand("SET", "MAX_TIMEOUT:" + maxTimeout); } + // Reservation commands (admin-only) + public void ReservationAdd(string player1Username, string player2Username) { + if (!IsAdmin) return; + if (!isValidReservationUsername(player1Username) || !isValidReservationUsername(player2Username)) { + GD.PrintErr("Invalid username(s) for reservation."); + return; + } + sendCommand("RESERVATION", $"ADD:{player1Username.Trim()},{player2Username.Trim()}"); + } + + public void ReservationDelete(string player1Username, string player2Username) { + if (!IsAdmin) return; + if (!isValidReservationUsername(player1Username) || !isValidReservationUsername(player2Username)) { + GD.PrintErr("Invalid username(s) for reservation."); + return; + } + sendCommand("RESERVATION", $"DELETE:{player1Username.Trim()},{player2Username.Trim()}"); + } + + public void ReservationGet() { + if (!IsAdmin) return; + sendCommand("RESERVATION", "GET"); + } + + private static bool isValidReservationUsername(string username) { + if (string.IsNullOrWhiteSpace(username)) return false; + // Protocol delimiters used by the server. + return !username.Contains(":") && !username.Contains(",") && !username.Contains("|"); + } + private void sendCommand(string header, string body = "") { if (!IsSocketOpen) { GD.PrintErr($"Cannot send {header}, socket is not open."); @@ -282,6 +321,7 @@ public partial class Connection : Node { IsAdmin = true; GetMoveWait(); GetTournamentStatus(); + ReservationGet(); OnBecomeAdmin?.Invoke(); } @@ -289,6 +329,9 @@ public partial class Connection : Node { case "TOURNAMENT": handleTournamentMessage(body); break; + case "RESERVATION": + handleReservationMessage(body); + break; case "GET": string data = body.Split(":")[1]; if (body.StartsWith("MOVE_WAIT")) { @@ -367,6 +410,64 @@ public partial class Connection : Node { } } + private void handleReservationMessage(string body) { + if (string.IsNullOrWhiteSpace(body)) { + return; + } + + string[] segments = body.Split(':'); + string command = segments[0].Trim().ToUpperInvariant(); + + switch (command) { + case "ADD": { + // ADD:, + if (segments.Length < 2) break; + string[] users = segments[1].Split(','); + if (users.Length != 2) break; + + var p1 = users[0]; + var p2 = users[1]; + Reservations.Add((p1, p2)); + OnReservationAddAck?.Invoke(); + OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations)); + break; + } + case "DELETE": { + // DELETE:, + if (segments.Length < 2) break; + string[] users = segments[1].Split(','); + if (users.Length != 2) break; + + var p1 = users[0]; + var p2 = users[1]; + Reservations.RemoveAll(r => r.player1 == p1 && r.player2 == p2); + OnReservationDeleteAck?.Invoke(); + OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations)); + break; + } + case "LIST": { + // LIST:,|,|... + var reservations = new List<(string player1, string player2)>(); + + if (segments.Length >= 2 && !string.IsNullOrWhiteSpace(segments[1])) { + string[] entries = segments[1].Split('|'); + foreach (string entry in entries) { + string[] users = entry.Split(','); + if (users.Length != 2) continue; + reservations.Add((users[0], users[1])); + } + } + + Reservations = reservations; + OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations)); + break; + } + default: + GD.PrintErr($"Unhandled RESERVATION message: {body}"); + break; + } + } + private void handlePlayerList(string body) { if (string.IsNullOrWhiteSpace(body)) { return; From b7a37a99f06856ec501e891dcdc815f4c087698c Mon Sep 17 00:00:00 2001 From: Rem's Little Helper Date: Wed, 4 Feb 2026 15:28:31 +0000 Subject: [PATCH 2/3] chore: align reservation API naming and polling --- scripts/Connection.cs | 35 ++++++++--------------------------- 1 file changed, 8 insertions(+), 27 deletions(-) diff --git a/scripts/Connection.cs b/scripts/Connection.cs index 6392f21..2eaf177 100644 --- a/scripts/Connection.cs +++ b/scripts/Connection.cs @@ -42,9 +42,6 @@ public partial class Connection : Node { public event Action OnGetDataAcks; public event Action OnSetDataAcks; - // Reservation system (admin-only server feature) - public event Action OnReservationAddAck; - public event Action OnReservationDeleteAck; public event Action> OnUpdatedReservations; public event Action OnWsConnectionSuccess; @@ -59,7 +56,6 @@ public partial class Connection : Node { public TournamentType ActiveTournament { get; private set; } public bool DemoMode { get; private set; } - // (player1, player2) tuples as reported by the server. public List<(string player1, string player2)> Reservations { get; private set; } = []; public List<(string, int)> PreviousMoves { get; private set; } = []; @@ -108,10 +104,12 @@ public partial class Connection : Node { OnWsConnectionSuccess?.Invoke(); UpdateGameList(); UpdatePlayerList(); + GetReservations(); refreshGamePlayerListTimer = 5.0f; } else if (refreshGamePlayerListTimer <= 0.0f) { UpdateGameList(); UpdatePlayerList(); + GetReservations(); refreshGamePlayerListTimer = 5.0f; } else { refreshGamePlayerListTimer -= (float)delta; @@ -228,36 +226,21 @@ public partial class Connection : Node { sendCommand("SET", "MAX_TIMEOUT:" + maxTimeout); } - // Reservation commands (admin-only) - public void ReservationAdd(string player1Username, string player2Username) { + public void AddReservation(string player1Username, string player2Username) { if (!IsAdmin) return; - if (!isValidReservationUsername(player1Username) || !isValidReservationUsername(player2Username)) { - GD.PrintErr("Invalid username(s) for reservation."); - return; - } - sendCommand("RESERVATION", $"ADD:{player1Username.Trim()},{player2Username.Trim()}"); + sendCommand("RESERVATION", $"ADD:{player1Username},{player2Username}"); } - public void ReservationDelete(string player1Username, string player2Username) { + public void DeleteReservation(string player1Username, string player2Username) { if (!IsAdmin) return; - if (!isValidReservationUsername(player1Username) || !isValidReservationUsername(player2Username)) { - GD.PrintErr("Invalid username(s) for reservation."); - return; - } - sendCommand("RESERVATION", $"DELETE:{player1Username.Trim()},{player2Username.Trim()}"); + sendCommand("RESERVATION", $"DELETE:{player1Username},{player2Username}"); } - public void ReservationGet() { + public void GetReservations() { if (!IsAdmin) return; sendCommand("RESERVATION", "GET"); } - private static bool isValidReservationUsername(string username) { - if (string.IsNullOrWhiteSpace(username)) return false; - // Protocol delimiters used by the server. - return !username.Contains(":") && !username.Contains(",") && !username.Contains("|"); - } - private void sendCommand(string header, string body = "") { if (!IsSocketOpen) { GD.PrintErr($"Cannot send {header}, socket is not open."); @@ -321,7 +304,7 @@ public partial class Connection : Node { IsAdmin = true; GetMoveWait(); GetTournamentStatus(); - ReservationGet(); + GetReservations(); OnBecomeAdmin?.Invoke(); } @@ -428,7 +411,6 @@ public partial class Connection : Node { var p1 = users[0]; var p2 = users[1]; Reservations.Add((p1, p2)); - OnReservationAddAck?.Invoke(); OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations)); break; } @@ -441,7 +423,6 @@ public partial class Connection : Node { var p1 = users[0]; var p2 = users[1]; Reservations.RemoveAll(r => r.player1 == p1 && r.player2 == p2); - OnReservationDeleteAck?.Invoke(); OnUpdatedReservations?.Invoke(new List<(string player1, string player2)>(Reservations)); break; } From b92bcc4db12043f90dabc194c632df75edb3965d Mon Sep 17 00:00:00 2001 From: Rem's Little Helper Date: Wed, 4 Feb 2026 15:56:13 +0000 Subject: [PATCH 3/3] chore: remove reservation protocol comments --- scripts/Connection.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/Connection.cs b/scripts/Connection.cs index 2eaf177..40c7dea 100644 --- a/scripts/Connection.cs +++ b/scripts/Connection.cs @@ -403,7 +403,6 @@ public partial class Connection : Node { switch (command) { case "ADD": { - // ADD:, if (segments.Length < 2) break; string[] users = segments[1].Split(','); if (users.Length != 2) break; @@ -415,7 +414,6 @@ public partial class Connection : Node { break; } case "DELETE": { - // DELETE:, if (segments.Length < 2) break; string[] users = segments[1].Split(','); if (users.Length != 2) break; @@ -427,7 +425,6 @@ public partial class Connection : Node { break; } case "LIST": { - // LIST:,|,|... var reservations = new List<(string player1, string player2)>(); if (segments.Length >= 2 && !string.IsNullOrWhiteSpace(segments[1])) {