diff --git a/SCHIZO/SwarmControl/ControlWebSocket.Plumbing.cs b/SCHIZO/SwarmControl/ControlWebSocket.Plumbing.cs index 3428d4f4..d245a1a9 100644 --- a/SCHIZO/SwarmControl/ControlWebSocket.Plumbing.cs +++ b/SCHIZO/SwarmControl/ControlWebSocket.Plumbing.cs @@ -65,7 +65,7 @@ private async Task SendThread() { try { - await SendStringInternal(JsonConvert.SerializeObject(msg, Formatting.None)); + await SendStringInternal(JsonConvert.SerializeObject(msg, Formatting.None), msg.MessageType != MessageType.Ping); if (msg.MessageType != MessageType.Ping) LOGGER.LogInfo($"Sent {msg.MessageType} ({msg.Guid})"); } @@ -103,6 +103,7 @@ private async Task PingThread() if (_sendQueue.IsEmpty) { SendMessage(new PingMessage()); + await Task.Delay(1000); } if (_lastPong.AddSeconds(5) < DateTime.UtcNow) { @@ -112,7 +113,7 @@ private async Task PingThread() } } - private async Task SendStringInternal(string message, CancellationToken ct = default) + private async Task SendStringInternal(string message, bool print = true, CancellationToken ct = default) { if (string.IsNullOrEmpty(message)) throw new ArgumentNullException(nameof(message)); @@ -120,7 +121,8 @@ private async Task SendStringInternal(string message, CancellationToken ct = def if (!CheckOpen()) throw new InvalidOperationException("Socket not connected"); - LOGGER.LogDebug($"SEND:\n{message}"); + if (print) + LOGGER.LogDebug($"SEND:\n{message}"); await _socket.SendAsync(new ArraySegment(Encoding.UTF8.GetBytes(message)), WebSocketMessageType.Text, true, ct); } @@ -131,7 +133,8 @@ public void SendMessage(GameMessage message) LOGGER.LogDebug($"Enqueued {message.MessageType} ({message.Guid})"); } - private async Task ReceiveStringAsync(CancellationToken ct = default) + // note: cancelling in ReceiveAsync kills the socket, there's an issue on this that's closed as "by design" Smile + private async Task ReceiveStringAsync(CancellationToken killSocketCt = default) { if (!CheckOpen()) return null; @@ -144,7 +147,7 @@ public void SendMessage(GameMessage message) int totalBytes = 0; while (result is not { EndOfMessage: true }) { - result = await _socket.ReceiveAsync(buffer, ct); + result = await _socket.ReceiveAsync(buffer, killSocketCt); ms.Write(buffer.Array, 0, result.Count); totalBytes += result.Count; } @@ -156,10 +159,10 @@ public void SendMessage(GameMessage message) case WebSocketMessageType.Text: return Encoding.UTF8.GetString(ms.ToArray()); case WebSocketMessageType.Binary: - await _socket.CloseOutputAsync(WebSocketCloseStatus.InvalidMessageType, "Unexpected binary data", ct); + await _socket.CloseOutputAsync(WebSocketCloseStatus.InvalidMessageType, "Unexpected binary data", killSocketCt); throw new InvalidDataException("Received unexpected binary data"); default: - await _socket.CloseOutputAsync(WebSocketCloseStatus.InvalidMessageType, "Invalid message type", ct); + await _socket.CloseOutputAsync(WebSocketCloseStatus.InvalidMessageType, "Invalid message type", killSocketCt); throw new InvalidDataException("Received invalid message type"); } } diff --git a/SCHIZO/SwarmControl/MessageProcessor.cs b/SCHIZO/SwarmControl/MessageProcessor.cs index 49dc140f..5e1b9f63 100644 --- a/SCHIZO/SwarmControl/MessageProcessor.cs +++ b/SCHIZO/SwarmControl/MessageProcessor.cs @@ -49,11 +49,15 @@ private void OnClose(WebSocketCloseStatus status, string? reason) private void OnHelloBack(HelloBackMessage helloBack) { - if (helloBack.Allowed) return; + if (!helloBack.Allowed) + { + LOGGER.LogError("Server rejected handshake"); + _socket.Disconnect().Start(); + uGUI.main.confirmation.Show("Server rejected handshake\nConnection is not possible"); + return; + } - LOGGER.LogError("Server rejected handshake"); - _socket.Disconnect().Start(); - uGUI.main.confirmation.Show("Server rejected handshake\nConnection is not possible"); + SwarmControlManager.Instance.SendIngameStateMsg(); } private void OnConsoleInput(ConsoleInputMessage msg) { diff --git a/SCHIZO/SwarmControl/Models/Game/Messages/IngameStateChangedMessage.cs b/SCHIZO/SwarmControl/Models/Game/Messages/IngameStateChangedMessage.cs index 47d58e72..62946c13 100644 --- a/SCHIZO/SwarmControl/Models/Game/Messages/IngameStateChangedMessage.cs +++ b/SCHIZO/SwarmControl/Models/Game/Messages/IngameStateChangedMessage.cs @@ -4,10 +4,8 @@ internal record IngameStateChangedMessage : GameMessage { public override MessageType MessageType => MessageType.IngameStateChanged; public bool Ingame { get; set; } - public bool Paused { get; set; } /// - /// Forbid spawning while inside a base or seatruck (prawn suit is fine) + /// Spawning is forbidden while on land or inside a base/seatruck (prawn suit is fine) /// - public bool Indoors { get; set; } - public bool OnLand { get; set; } + public bool CanSpawn { get; set; } } diff --git a/SCHIZO/SwarmControl/Redeems/Neuro/RerollPlayerName.cs b/SCHIZO/SwarmControl/Redeems/Neuro/RerollPlayerName.cs deleted file mode 100644 index ac066fab..00000000 --- a/SCHIZO/SwarmControl/Redeems/Neuro/RerollPlayerName.cs +++ /dev/null @@ -1,57 +0,0 @@ -using System.Collections.Generic; -using SCHIZO.Commands.Attributes; -using SCHIZO.Commands.Base; - -namespace SCHIZO.SwarmControl.Redeems.Neuro; - -#nullable enable -[CommandCategory("Neuro")] -[Redeem( - Name = "redeem_rerollname", - DisplayName = "Reroll Vedal's Name", - Description = "Reroll Vedal's name in the Neuro integration." -)] -internal class RerollPlayerName() : ProxyCommand(SetPlayerName.COMMAND) -{ - public static readonly string[] Names = [ - "Vedal", - "Vega", - "Bucket", - "Vedalopalypse", - "Vedalitaire", - "Vadel", - "Vercel", - "Vorpal", - "Vedalite", - "Vita", - "Voronoi", - "Vernal", - "Tutel", - "Visage", - "Dalveed", - "Harrison Temple", - "Hypervedal", - "Mosquito987", - "Coldfish", - "Ladev", - "Vedeal", - "Veed", - "Ved", - "Waddle", - "Weedle", - "Wordle", - "Veddie", - "Vedalyn", - "Vortex", - "Lemon" - ]; - - public override IReadOnlyList Parameters => []; - - protected override Dictionary? GetTargetArgs(Dictionary? proxyArgs) - { - Dictionary args = proxyArgs ?? []; - args["name"] = Names[UnityEngine.Random.Range(0, Names.Length-1)]; - return args; - } -} diff --git a/SCHIZO/SwarmControl/Redeems/Neuro/SetPlayerName.cs b/SCHIZO/SwarmControl/Redeems/Neuro/SetPlayerName.cs deleted file mode 100644 index 87d78f61..00000000 --- a/SCHIZO/SwarmControl/Redeems/Neuro/SetPlayerName.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Collections.Generic; -using SCHIZO.Commands.Attributes; -using SCHIZO.Commands.Base; -using SCHIZO.Commands.ConsoleCommands; -using SwarmControl.Models.Game; - -namespace SCHIZO.SwarmControl.Redeems.Neuro; - -[CommandCategory("Neuro")] -[Redeem(Name = COMMAND, - DisplayName = "Set Vedal's Name", - Description = "Set Vedal's name in the Neuro integration." -)] -internal class SetPlayerName() : ConsoleWrapperCommand("immersion set player") -{ - public const string COMMAND = "redeem_setname"; - - public override IReadOnlyList Parameters => [ - new TextParameter(new NamedModel("name")) - ]; -} diff --git a/SCHIZO/SwarmControl/Redeems/Spawns/SpawnFiltered.cs b/SCHIZO/SwarmControl/Redeems/Spawns/SpawnFiltered.cs index fe48b454..52d15e41 100644 --- a/SCHIZO/SwarmControl/Redeems/Spawns/SpawnFiltered.cs +++ b/SCHIZO/SwarmControl/Redeems/Spawns/SpawnFiltered.cs @@ -2,7 +2,9 @@ using System.Collections.Generic; using SCHIZO.Commands.Base; using SCHIZO.Commands.ConsoleCommands; +using SCHIZO.Commands.Context; using SCHIZO.Commands.Input; +using SCHIZO.Commands.Output; namespace SCHIZO.SwarmControl.Redeems.Spawns; @@ -26,6 +28,13 @@ public SpawnFiltered() : base("spawn") ]; } + protected override object? ExecuteCore(CommandExecutionContext ctx) + { + if (!SwarmControlManager.Instance.CanSpawn) + return CommonResults.Error("Cannot spawn at this time."); + return base.ExecuteCore(ctx); + } + protected override Dictionary? GetTargetArgs(Dictionary? proxyArgs) { if (proxyArgs is null) diff --git a/SCHIZO/SwarmControl/SwarmControlManager.cs b/SCHIZO/SwarmControl/SwarmControlManager.cs index 83378020..256387db 100644 --- a/SCHIZO/SwarmControl/SwarmControlManager.cs +++ b/SCHIZO/SwarmControl/SwarmControlManager.cs @@ -4,10 +4,13 @@ using System.Net.WebSockets; using System.Threading; using System.Threading.Tasks; +using Microsoft.Cci; +using Nautilus.Utility; using SCHIZO.Attributes; using SCHIZO.Commands.Attributes; using SCHIZO.Helpers; using SCHIZO.Resources; +using SwarmControl.Models.Game.Messages; using TMPro; using UnityEngine; @@ -37,11 +40,28 @@ public string PrivateApiKey private ControlWebSocket _socket; + public bool Ingame { get; private set; } + public bool CanSpawn { get; private set; } + private void Awake() { Instance = this; _socket = new(); Processor = new(_socket); + SaveUtils.RegisterOnFinishLoadingEvent(OnLoad); + SaveUtils.RegisterOnQuitEvent(OnQuit); + } + + private void OnLoad() + { + Ingame = true; + SendIngameStateMsg(); + } + + private void OnQuit() + { + Ingame = false; + SendIngameStateMsg(); } private void OnDestroy() @@ -159,15 +179,26 @@ private void Update() action(); } + private void FixedUpdate() + { + bool canSpawn = Player.main + && Player.main.IsUnderwaterForSwimming() + && !Player.main.IsPilotingSeatruck() + && !Player.main.IsInBase() + ; + if (canSpawn != CanSpawn) + { + CanSpawn = canSpawn; + SendIngameStateMsg(); + } + } + private int _retries; private const int MaxRetries = int.MaxValue; private IEnumerator AutoReconnectCoro() { while (_retries < MaxRetries) { - // unfortunately the socket state stays "Open" even if the server stops replying to pings - // so detecting disconnects robustly is left as a "fun" exercise for the reader - // no you can't just ReceiveAsync with a timeout ct because cancelling it kills the socket, there's an issue on this that's closed as "by design" Smile if (_socket.IsConnected) { _retries = 0; @@ -185,4 +216,16 @@ private IEnumerator AutoReconnectCoro() LOGGER.LogWarning("Could not reconnect to websocket"); ShowConfirmation("Lost connection to server\nReconnect in options"); } + + internal void SendIngameStateMsg() + { + if (_socket.IsConnected) + { + _socket.SendMessage(new IngameStateChangedMessage() + { + Ingame = Ingame, + CanSpawn = CanSpawn, + }); + } + } }