From daa64f247c833d2adf1e94a1536e20ec4df2c51c Mon Sep 17 00:00:00 2001 From: Dankrushen Date: Wed, 6 Jan 2021 19:12:36 -0500 Subject: [PATCH] Update message receiving & add restart command --- MultiAdmin/EventInterfaces.cs | 10 +++++ MultiAdmin/ModFeatures.cs | 5 ++- MultiAdmin/Server.cs | 27 +++++++++--- MultiAdmin/ServerIO/OutputHandler.cs | 66 ++++++++++++++++++++++------ MultiAdmin/ServerIO/ServerSocket.cs | 49 ++++++++++++++++++--- 5 files changed, 129 insertions(+), 28 deletions(-) diff --git a/MultiAdmin/EventInterfaces.cs b/MultiAdmin/EventInterfaces.cs index 7f73d22..2261542 100644 --- a/MultiAdmin/EventInterfaces.cs +++ b/MultiAdmin/EventInterfaces.cs @@ -49,6 +49,16 @@ public interface IEventServerFull : IMAEvent void OnServerFull(); } + public interface IEventIdleEnter : IMAEvent + { + void OnIdleEnter(); + } + + public interface IEventIdleExit : IMAEvent + { + void OnIdleExit(); + } + public interface ICommand { void OnCall(string[] args); diff --git a/MultiAdmin/ModFeatures.cs b/MultiAdmin/ModFeatures.cs index 2fcbe45..e7975a7 100644 --- a/MultiAdmin/ModFeatures.cs +++ b/MultiAdmin/ModFeatures.cs @@ -10,7 +10,10 @@ public enum ModFeatures // Replaces detecting game output with MultiAdmin events for game events CustomEvents = 1 << 0, + // Uses a custom restart command ("RESTART") + RestartCommand = 2 << 0, + // Supporting all current features - All = ~(~0 << 1) + All = ~(~0 << 2) } } diff --git a/MultiAdmin/Server.cs b/MultiAdmin/Server.cs index 89b2361..3a4994d 100644 --- a/MultiAdmin/Server.cs +++ b/MultiAdmin/Server.cs @@ -379,7 +379,9 @@ public void StartServer(bool restartOnCrash = true) // Start the output reader OutputHandler outputHandler = new OutputHandler(this); + // Assign the socket events to the OutputHandler consoleSocket.OnReceiveMessage += outputHandler.HandleMessage; + consoleSocket.OnReceiveAction += outputHandler.HandleAction; // Finally, start the game GameProcess = Process.Start(startInfo); @@ -426,6 +428,10 @@ public void StartServer(bool restartOnCrash = true) consoleSocket.Disconnect(); + // Remove the socket events for OutputHandler + consoleSocket.OnReceiveMessage -= outputHandler.HandleMessage; + consoleSocket.OnReceiveAction -= outputHandler.HandleAction; + SessionSocket = null; StartDateTime = null; } @@ -476,22 +482,29 @@ public void StopServer(bool killGame = false) ForEachHandler(stopEvent => stopEvent.OnServerStop()); - if (killGame && IsGameProcessRunning) - GameProcess.Kill(); - else if (!SendMessage("QUIT")) + if ((killGame || !SendMessage("QUIT")) && IsGameProcessRunning) GameProcess.Kill(); } - public void SoftRestartServer() + public void SoftRestartServer(bool killGame = false) { if (!IsRunning) throw new Exceptions.ServerNotRunningException(); initRestartTimeoutTime = DateTime.Now; Status = ServerStatus.Restarting; - SendMessage("ROUNDRESTART"); - if (!SendMessage("QUIT")) - GameProcess.Kill(); + if (supportedModFeatures.HasFlag(ModFeatures.RestartCommand)) + { + SendMessage("RESTART"); + if (killGame && IsGameProcessRunning) + GameProcess.Kill(); + } + else + { + SendMessage("ROUNDRESTART"); + if ((killGame || !SendMessage("QUIT")) && IsGameProcessRunning) + GameProcess.Kill(); + } } #endregion diff --git a/MultiAdmin/ServerIO/OutputHandler.cs b/MultiAdmin/ServerIO/OutputHandler.cs index d96acf6..fa6c526 100644 --- a/MultiAdmin/ServerIO/OutputHandler.cs +++ b/MultiAdmin/ServerIO/OutputHandler.cs @@ -1,5 +1,4 @@ using System; -using System.Globalization; using System.Text.RegularExpressions; using MultiAdmin.ConsoleTools; using MultiAdmin.Utility; @@ -15,27 +14,35 @@ public class OutputHandler private readonly Server server; + private enum OutputCodes : byte + { + //0x00 - 0x0F - reserved for colors + + RoundRestart = 0x10, + IdleEnter = 0x11, + IdleExit = 0x12, + ExitActionReset = 0x13, + ExitActionShutdown = 0x14, + ExitActionSilentShutdown = 0x15, + ExitActionRestart = 0x16 + } + public OutputHandler(Server server) { this.server = server; } - public void HandleMessage(object source, string message) + public void HandleMessage(object source, ServerSocket.MessageEventArgs message) { - if (message == null) + if (message.message == null) return; - ColoredMessage coloredMessage = new ColoredMessage(message, ConsoleColor.White); + ColoredMessage coloredMessage = new ColoredMessage(message.message, ConsoleColor.White); if (!coloredMessage.text.IsEmpty()) { - // Convert the first character to the corresponding color - if (byte.TryParse(coloredMessage.text[0].ToString(), NumberStyles.HexNumber, - NumberFormatInfo.CurrentInfo, out byte consoleColor)) - { - coloredMessage.textColor = (ConsoleColor)consoleColor; - coloredMessage.text = coloredMessage.text.Substring(1); - } + // Parse the color byte + coloredMessage.textColor = (ConsoleColor)message.color; // Smod2 loggers pretty printing Match match = SmodRegex.Match(coloredMessage.text); @@ -91,11 +98,12 @@ public void HandleMessage(object source, string message) server.ForEachHandler(roundEnd => roundEnd.OnRoundEnd()); break; + /* Replaced by OutputCodes.RoundRestart case "waiting for players": server.IsLoading = false; - server.ForEachHandler(waitingForPlayers => waitingForPlayers.OnWaitingForPlayers()); break; + */ case "new round has been started": server.ForEachHandler(roundStart => roundStart.OnRoundStart()); @@ -128,11 +136,12 @@ public void HandleMessage(object source, string message) server.ForEachHandler(roundEnd => roundEnd.OnRoundEnd()); break; + /* Replaced by OutputCodes.RoundRestart case "waiting-for-players-event": server.IsLoading = false; - server.ForEachHandler(waitingForPlayers => waitingForPlayers.OnWaitingForPlayers()); break; + */ case "round-start-event": server.ForEachHandler(roundStart => roundStart.OnRoundStart()); @@ -161,5 +170,36 @@ public void HandleMessage(object source, string message) server.Write(coloredMessage); } + + public void HandleAction(object source, byte action) + { + switch ((OutputCodes)action) + { + // This seems to show up at the waiting for players event + case OutputCodes.RoundRestart: + server.IsLoading = false; + server.ForEachHandler(waitingForPlayers => waitingForPlayers.OnWaitingForPlayers()); + break; + + case OutputCodes.IdleEnter: + server.ForEachHandler(idleEnter => idleEnter.OnIdleEnter()); + break; + + case OutputCodes.IdleExit: + server.ForEachHandler(idleExit => idleExit.OnIdleExit()); + break; + + // Unhandled for now + case OutputCodes.ExitActionReset: + case OutputCodes.ExitActionShutdown: + case OutputCodes.ExitActionSilentShutdown: + case OutputCodes.ExitActionRestart: + break; + + default: + Program.LogDebug(nameof(HandleAction), $"Received unknown output code ({action}), is MultiAdmin up to date? This error can probably be safely ignored."); + break; + } + } } } diff --git a/MultiAdmin/ServerIO/ServerSocket.cs b/MultiAdmin/ServerIO/ServerSocket.cs index 19c028f..ece55cd 100644 --- a/MultiAdmin/ServerIO/ServerSocket.cs +++ b/MultiAdmin/ServerIO/ServerSocket.cs @@ -5,6 +5,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using MultiAdmin.ConsoleTools; namespace MultiAdmin.ServerIO { @@ -21,7 +22,20 @@ public class ServerSocket : IDisposable private TcpClient client; private NetworkStream networkStream; - public event EventHandler OnReceiveMessage; + public struct MessageEventArgs + { + public MessageEventArgs(string message, byte color) + { + this.message = message; + this.color = color; + } + + public readonly string message; + public readonly byte color; + } + + public event EventHandler OnReceiveMessage; + public event EventHandler OnReceiveAction; public int Port => ((IPEndPoint)listener.LocalEndpoint).Port; @@ -61,31 +75,52 @@ public void Connect() public async void MessageListener() { + byte[] typeBuffer = new byte[1]; byte[] intBuffer = new byte[IntBytes]; while (!disposed && networkStream != null) { try { + int messageTypeBytesRead = + await networkStream.ReadAsync(typeBuffer, 0, 1, disposeCancellationSource.Token); + + // Socket has been disconnected + if (messageTypeBytesRead <= 0) + { + Disconnect(); + break; + } + + byte messageType = typeBuffer[0]; + + // 16 colors reserved, otherwise process as control message (action) + if (messageType >= 16) + { + OnReceiveAction?.Invoke(this, messageType); + continue; + } + int lengthBytesRead = await networkStream.ReadAsync(intBuffer, 0, IntBytes, disposeCancellationSource.Token); - // Socket has been disconnected - if (lengthBytesRead <= 0) + // Socket has been disconnected or integer read is invalid + if (lengthBytesRead != IntBytes) { Disconnect(); break; } - int length = BitConverter.ToInt32(intBuffer, 0); + // Decode integer + int length = (intBuffer[0] << 24) | (intBuffer[1] << 16) | (intBuffer[2] << 8) | intBuffer[3]; // Handle empty messages asap if (length == 0) { - OnReceiveMessage?.Invoke(this, ""); + OnReceiveMessage?.Invoke(this, new MessageEventArgs("", messageType)); } else if (length < 0) { - OnReceiveMessage?.Invoke(this, null); + OnReceiveMessage?.Invoke(this, new MessageEventArgs(null, messageType)); } byte[] messageBuffer = new byte[length]; @@ -101,7 +136,7 @@ public async void MessageListener() string message = Encoding.GetString(messageBuffer, 0, length); - OnReceiveMessage?.Invoke(this, message); + OnReceiveMessage?.Invoke(this, new MessageEventArgs(message, messageType)); } catch (Exception e) {