From 74bad02b94b1b9d86e1f215214e90644f62b546e Mon Sep 17 00:00:00 2001 From: Brett Hewitson Date: Sun, 8 May 2022 01:08:23 +1000 Subject: [PATCH] Rcon Changes Rcon Window - minor changes to the player list, online player count and server status. Online Player Count - fixed the online player count to display the correct value. --- .../Globalization/en-US/en-US.xaml | 4 +- src/ARKServerManager/Lib/ServerRCON.cs | 422 +++++++++--------- .../Lib/ServerStatusWatcher.cs | 3 +- src/ARKServerManager/VersionFeed.xml | 7 +- src/ARKServerManager/VersionFeedBeta.xml | 25 +- .../Windows/RCONWindow.xaml.cs | 15 +- .../Globalization/en-US/en-US.xaml | 4 +- src/ConanServerManager/Lib/ServerRcon.cs | 265 +++++++---- .../Lib/ServerStatusWatcher.cs | 3 +- .../Lib/ViewModel/PlayerInfo.cs | 2 +- src/ConanServerManager/VersionFeed.xml | 10 +- src/ConanServerManager/VersionFeedBeta.xml | 26 +- .../Windows/RconWindow.xaml.cs | 9 +- 13 files changed, 454 insertions(+), 341 deletions(-) diff --git a/src/ARKServerManager/Globalization/en-US/en-US.xaml b/src/ARKServerManager/Globalization/en-US/en-US.xaml index ff7555cc..52a3edc3 100644 --- a/src/ARKServerManager/Globalization/en-US/en-US.xaml +++ b/src/ARKServerManager/Globalization/en-US/en-US.xaml @@ -2607,8 +2607,8 @@ Disconnected Connected - Player '{0}' joined the game. - Player '{0}' left the game. + '{0}' joined the game. + '{0}' left the game. diff --git a/src/ARKServerManager/Lib/ServerRCON.cs b/src/ARKServerManager/Lib/ServerRCON.cs index 4f498db2..3f2a0b78 100644 --- a/src/ARKServerManager/Lib/ServerRCON.cs +++ b/src/ARKServerManager/Lib/ServerRCON.cs @@ -1,5 +1,6 @@ using ArkData; using NLog; +using ServerManagerTool.Common.Enums; using ServerManagerTool.Common.Extensions; using ServerManagerTool.Common.Interfaces; using ServerManagerTool.Common.Lib; @@ -24,8 +25,6 @@ namespace ServerManagerTool.Lib { public class ServerRCON : DependencyObject, IAsyncDisposable { - public event EventHandler PlayersCollectionUpdated; - private const int STEAM_UPDATE_INTERVAL = 60; private const int PLAYER_LIST_INTERVAL = 5000; private const int GET_CHAT_INTERVAL = 1000; @@ -38,36 +37,7 @@ namespace ServerManagerTool.Lib public const string RCON_COMMAND_SERVERCHAT = "serverchat"; public const string RCON_COMMAND_WILDDINOWIPE = "DestroyWildDinos"; - [TypeConverter(typeof(EnumDescriptionTypeConverter))] - public enum ConsoleStatus - { - Disconnected, - Connected, - }; - - public class ConsoleCommand - { - public ConsoleStatus status; - public string rawCommand; - - public string command; - public string args; - - public bool suppressCommand; - public bool suppressOutput; - public IEnumerable lines = new string[0]; - }; - - private class CommandListener : IDisposable - { - public Action Callback { get; set; } - public Action DisposeAction { get; set; } - - public void Dispose() - { - DisposeAction(this); - } - } + public event EventHandler PlayersCollectionUpdated; public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(nameof(Status), typeof(ConsoleStatus), typeof(ServerRCON), new PropertyMetadata(ConsoleStatus.Disconnected)); public static readonly DependencyProperty PlayersProperty = DependencyProperty.Register(nameof(Players), typeof(SortableObservableCollection), typeof(ServerRCON), new PropertyMetadata(null)); @@ -77,16 +47,18 @@ namespace ServerManagerTool.Lib private static readonly char[] lineSplitChars = new char[] { '\n' }; private static readonly char[] argsSplitChars = new char[] { ' ' }; - private readonly ActionQueue commandProcessor = new ActionQueue(TaskScheduler.Default); - private readonly ActionQueue outputProcessor = new ActionQueue(TaskScheduler.FromCurrentSynchronizationContext()); - private readonly List commandListeners = new List(); - private readonly RCONParameters rconParams; - private QueryMaster.Rcon console; - private int maxCommandRetries = 3; - private readonly ConcurrentDictionary players = new ConcurrentDictionary(); - private readonly object updatePlayerCollectionLock = new object(); - private CancellationTokenSource cancellationTokenSource = null; + private readonly ActionQueue _commandProcessor = new ActionQueue(TaskScheduler.Default); + private readonly ActionQueue _outputProcessor = new ActionQueue(TaskScheduler.FromCurrentSynchronizationContext()); + private readonly List _commandListeners = new List(); + + private readonly RCONParameters _rconParameters; + private QueryMaster.Rcon _console; + private int _maxCommandRetries = 3; + + private readonly ConcurrentDictionary _players = new ConcurrentDictionary(); + private readonly object _updatePlayerCollectionLock = new object(); + private CancellationTokenSource _cancellationTokenSource = null; private readonly Logger _chatLogger; private readonly Logger _allLogger; @@ -97,28 +69,30 @@ namespace ServerManagerTool.Lib public ServerRCON(RCONParameters parameters) { - this.rconParams = parameters; - this.Players = new SortableObservableCollection(); + _rconParameters = parameters; - _allLogger = App.GetProfileLogger(this.rconParams.ProfileId, "RCON_All", LogLevel.Info, LogLevel.Info); - _chatLogger = App.GetProfileLogger(this.rconParams.ProfileId, "RCON_Chat", LogLevel.Info, LogLevel.Info); - _eventLogger = App.GetProfileLogger(this.rconParams.ProfileId, "RCON_Event", LogLevel.Info, LogLevel.Info); - _debugLogger = App.GetProfileLogger(this.rconParams.ProfileId, "RCON_Debug", LogLevel.Trace, LogLevel.Debug); - _errorLogger = App.GetProfileLogger(this.rconParams.ProfileId, "RCON_Error", LogLevel.Error, LogLevel.Fatal); + _allLogger = App.GetProfileLogger(_rconParameters.ProfileId, "RCON_All", LogLevel.Info, LogLevel.Info); + _chatLogger = App.GetProfileLogger(_rconParameters.ProfileId, "RCON_Chat", LogLevel.Info, LogLevel.Info); + _eventLogger = App.GetProfileLogger(_rconParameters.ProfileId, "RCON_Event", LogLevel.Info, LogLevel.Info); + _debugLogger = App.GetProfileLogger(_rconParameters.ProfileId, "RCON_Debug", LogLevel.Trace, LogLevel.Debug); + _errorLogger = App.GetProfileLogger(_rconParameters.ProfileId, "RCON_Error", LogLevel.Error, LogLevel.Fatal); + + this.Players = new SortableObservableCollection(); } public async Task DisposeAsync() { - if (cancellationTokenSource != null) + if (_cancellationTokenSource != null) { - cancellationTokenSource.Cancel(); + _cancellationTokenSource.Cancel(); } - await this.commandProcessor.DisposeAsync(); - await this.outputProcessor.DisposeAsync(); - for (int index = this.commandListeners.Count - 1; index >= 0; index--) + await _commandProcessor.DisposeAsync(); + await _outputProcessor.DisposeAsync(); + + for (int index = _commandListeners.Count - 1; index >= 0; index--) { - this.commandListeners[index].Dispose(); + _commandListeners[index].Dispose(); } _disposed = true; @@ -159,8 +133,8 @@ namespace ServerManagerTool.Lib #region Methods public void Initialize() { - commandProcessor.PostAction(AutoPlayerList); - commandProcessor.PostAction(AutoGetChat); + _commandProcessor.PostAction(AutoPlayerList); + _commandProcessor.PostAction(AutoGetChat); UpdatePlayersAsync().DoNotWait(); } @@ -189,50 +163,55 @@ namespace ServerManagerTool.Lib private bool Reconnect() { - if (this.console != null) + if (_console != null) { - this.console.Dispose(); - this.console = null; + _console.Dispose(); + _console = null; } - var endpoint = new IPEndPoint(this.rconParams.RCONHostIP, this.rconParams.RCONPort); + var endpoint = new IPEndPoint(_rconParameters.RCONHostIP, _rconParameters.RCONPort); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endpoint); - this.console = server.GetControl(this.rconParams.RCONPassword); - return this.console != null; + _console = server.GetControl(_rconParameters.RCONPassword); + return _console != null; } public IDisposable RegisterCommandListener(Action callback) { var listener = new CommandListener { Callback = callback, DisposeAction = UnregisterCommandListener }; - this.commandListeners.Add(listener); + _commandListeners.Add(listener); return listener; } private void UnregisterCommandListener(CommandListener listener) { - this.commandListeners.Remove(listener); + _commandListeners.Remove(listener); } #endregion #region Process Methods private Task AutoPlayerList() { - return commandProcessor.PostAction(() => + return _commandProcessor.PostAction(() => { ProcessInput(new ConsoleCommand() { rawCommand = RCON_COMMAND_LISTPLAYERS, suppressCommand = true, suppressOutput = true }); - Task.Delay(PLAYER_LIST_INTERVAL).ContinueWith(t => commandProcessor.PostAction(AutoPlayerList)).DoNotWait(); + Task.Delay(PLAYER_LIST_INTERVAL).ContinueWith(t => _commandProcessor.PostAction(AutoPlayerList)).DoNotWait(); }); } private Task AutoGetChat() { - return commandProcessor.PostAction(() => + return _commandProcessor.PostAction(() => { ProcessInput(new ConsoleCommand() { rawCommand = RCON_COMMAND_GETCHAT, suppressCommand = true, suppressOutput = true }); - Task.Delay(GET_CHAT_INTERVAL).ContinueWith(t => commandProcessor.PostAction(AutoGetChat)).DoNotWait(); + Task.Delay(GET_CHAT_INTERVAL).ContinueWith(t => _commandProcessor.PostAction(AutoGetChat)).DoNotWait(); }); } + public Task IssueCommand(string userCommand) + { + return _commandProcessor.PostAction(() => ProcessInput(new ConsoleCommand() { rawCommand = userCommand })); + } + private bool ProcessInput(ConsoleCommand command) { try @@ -277,14 +256,14 @@ namespace ServerManagerTool.Lib command.status = ConsoleStatus.Connected; - this.outputProcessor.PostAction(() => ProcessOutput(command)); + _outputProcessor.PostAction(() => ProcessOutput(command)); return true; } catch (Exception ex) { _errorLogger.Error($"Failed to send command '{command.rawCommand}'. {ex.Message}"); command.status = ConsoleStatus.Disconnected; - this.outputProcessor.PostAction(() => ProcessOutput(command)); + _outputProcessor.PostAction(() => ProcessOutput(command)); return false; } } @@ -299,11 +278,6 @@ namespace ServerManagerTool.Lib NotifyCommand(command); } - public Task IssueCommand(string userCommand) - { - return this.commandProcessor.PostAction(() => ProcessInput(new ConsoleCommand() { rawCommand = userCommand })); - } - // This is bound to the UI thread private void HandleCommand(ConsoleCommand command) { @@ -320,8 +294,8 @@ namespace ServerManagerTool.Lib // // Update the visible player list // - command.suppressOutput = false; command.lines = HandleListPlayersCommand(command.lines); + command.suppressOutput = command.lines.Count() == 0; } if (command.command.Equals(RCON_COMMAND_GETCHAT, StringComparison.OrdinalIgnoreCase)) @@ -361,11 +335,13 @@ namespace ServerManagerTool.Lib private List HandleListPlayersCommand(IEnumerable commandLines) { var output = new List(); + var onlinePlayers = new List(); - if (commandLines != null) + var playerLines = commandLines?.ToList() ?? new List(); + if (playerLines.Count > 0) { - var onlinePlayers = new List(); - foreach (var line in commandLines) + + foreach (var line in playerLines) { var elements = line.Split(','); if (elements.Length != 2) @@ -373,9 +349,6 @@ namespace ServerManagerTool.Lib continue; var id = elements[1]?.Trim(); - if (id.Any(c => !char.IsDigit(c))) - // Invalid data. Ignore it. - continue; if (onlinePlayers.FirstOrDefault(p => p.PlayerId.Equals(id, StringComparison.OrdinalIgnoreCase)) != null) // Duplicate data. Ignore it. @@ -390,43 +363,56 @@ namespace ServerManagerTool.Lib onlinePlayers.Add(newPlayer); var playerJoined = false; - this.players.AddOrUpdate(newPlayer.PlayerId, (k) => { playerJoined = true; return newPlayer; }, (k, v) => { playerJoined = !v.IsOnline; v.IsOnline = true; return v; }); + _players.AddOrUpdate(newPlayer.PlayerId, + (k) => + { + playerJoined = true; + return newPlayer; + }, + (k, v) => + { + playerJoined = !v.IsOnline; + v.PlayerName = newPlayer.PlayerName; + v.IsOnline = newPlayer.IsOnline; + return v; + } + ); if (playerJoined) { - var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Join") ?? "Player '{0}' joined the game."; + var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Join") ?? "'{0}' joined the game."; var message = string.Format(messageFormat, newPlayer.PlayerName); output.Add(message); LogEvent(LogEventType.Event, message); LogEvent(LogEventType.All, message); } } - - var droppedPlayers = this.players.Values.Where(p => onlinePlayers.FirstOrDefault(np => np.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null); - foreach (var droppedPlayer in droppedPlayers) - { - if (droppedPlayer.IsOnline) - { - droppedPlayer.IsOnline = false; - - var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Leave") ?? "Player '{0}' left the game."; - var message = string.Format(messageFormat, droppedPlayer.PlayerName); - output.Add(message); - LogEvent(LogEventType.Event, message); - LogEvent(LogEventType.All, message); - } - } - - UpdatePlayerCollection(); } + var droppedPlayers = _players.Values.Where(p => onlinePlayers.FirstOrDefault(np => np.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null); + foreach (var droppedPlayer in droppedPlayers) + { + if (droppedPlayer.IsOnline) + { + droppedPlayer.IsOnline = false; + + var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Leave") ?? "'{0}' left the game."; + var message = string.Format(messageFormat, droppedPlayer.PlayerName); + output.Add(message); + LogEvent(LogEventType.Event, message); + LogEvent(LogEventType.All, message); + } + } + + UpdatePlayerCollection(); + return output; } // This is bound to the UI thread private void NotifyCommand(ConsoleCommand command) { - foreach (var listener in commandListeners) + foreach (var listener in _commandListeners) { try { @@ -446,13 +432,13 @@ namespace ServerManagerTool.Lib Exception lastException = null; int retries = 0; - while (retries < maxCommandRetries) + while (retries < _maxCommandRetries) { - if (this.console != null) + if (_console != null) { try { - return this.console.SendCommand(command); + return _console.SendCommand(command); } catch (Exception ex) { @@ -475,152 +461,158 @@ namespace ServerManagerTool.Lib retries++; } - this.maxCommandRetries = 10; - _errorLogger.Error($"Failed to connect to RCON at {this.rconParams.RCONHostIP}:{this.rconParams.RCONPort} with {this.rconParams.RCONPassword}. {lastException.Message}"); - throw new Exception($"Command failed to send after {maxCommandRetries} attempts. Last exception: {lastException.Message}", lastException); + _maxCommandRetries = 10; + _errorLogger.Error($"Failed to connect to RCON at {_rconParameters.RCONHostIP}:{_rconParameters.RCONPort} with {_rconParameters.RCONPassword}. {lastException.Message}"); + throw new Exception($"Command failed to send after {_maxCommandRetries} attempts. Last exception: {lastException.Message}", lastException); } #endregion private async Task UpdatePlayersAsync() { - if (this._disposed) + if (_disposed) return; - cancellationTokenSource = new CancellationTokenSource(); - var token = cancellationTokenSource.Token; + _cancellationTokenSource = new CancellationTokenSource(); - await UpdatePlayerDetailsAsync(cancellationTokenSource.Token) - .ContinueWith(async t1 => - { - await TaskUtils.RunOnUIThreadAsync(() => - { - UpdatePlayerCollection(); - }); - }, TaskContinuationOptions.NotOnCanceled) - .ContinueWith(t2 => - { - var cancelled = cancellationTokenSource.IsCancellationRequested; - cancellationTokenSource.Dispose(); - cancellationTokenSource = null; + await UpdatePlayerDetailsAsync(_cancellationTokenSource.Token); + var cancelled = _cancellationTokenSource.IsCancellationRequested; - if (!cancelled) - Task.Delay(PLAYER_LIST_INTERVAL).ContinueWith(t3 => UpdatePlayersAsync()); - }); + if (!cancelled) + { + await TaskUtils.RunOnUIThreadAsync(() => UpdatePlayerCollection()); + } + + _cancellationTokenSource.Dispose(); + _cancellationTokenSource = null; + + if (!cancelled) + { + await Task.Delay(PLAYER_LIST_INTERVAL) + .ContinueWith(t => UpdatePlayersAsync()); + } } private async Task UpdatePlayerDetailsAsync(CancellationToken token) { - if (!string.IsNullOrWhiteSpace(rconParams.InstallDirectory)) + if (_disposed) + return; + + if (string.IsNullOrWhiteSpace(_rconParameters.InstallDirectory)) + return; + + DataContainer dataContainer = null; + + try { - var savedPath = ServerProfile.GetProfileSavePath(rconParams.InstallDirectory, rconParams.AltSaveDirectoryName, rconParams.PGM_Enabled, rconParams.PGM_Name); - DataContainer dataContainer = null; - DateTime lastSteamUpdateUtc = DateTime.MinValue; + var savedPath = ServerProfile.GetProfileSavePath(_rconParameters.InstallDirectory, _rconParameters.AltSaveDirectoryName, _rconParameters.PGM_Enabled, _rconParameters.PGM_Name); - try - { - // load the player data from the files. - dataContainer = await DataContainer.CreateAsync(savedPath, savedPath); - } - catch (Exception ex) - { - _errorLogger.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: CreateAsync. {ex.Message}\r\n{ex.StackTrace}"); - return; - } + // load the player data from the files. + dataContainer = await DataContainer.CreateAsync(savedPath, savedPath); + } + catch (Exception ex) + { + _errorLogger.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: CreateAsync. {ex.Message}\r\n{ex.StackTrace}"); + return; + } - token.ThrowIfCancellationRequested(); - await Task.Run(() => - { - // update the player data with the latest update value from the players collection - foreach (var playerData in dataContainer.Players) - { - var id = playerData.PlayerId; - if (string.IsNullOrWhiteSpace(id)) - continue; + if (token.IsCancellationRequested) + return; - this.players.TryGetValue(id, out PlayerInfo player); - player?.UpdatePlatformData(playerData); - } - }, token); - - try - { - // load the player data from steam - lastSteamUpdateUtc = await dataContainer.LoadSteamAsync(SteamUtils.SteamWebApiKey, STEAM_UPDATE_INTERVAL); - } - catch (Exception ex) - { - _errorLogger.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: LoadSteamAsync. {ex.Message}\r\n{ex.StackTrace}"); - } - - token.ThrowIfCancellationRequested(); - - var totalPlayers = dataContainer.Players.Count; + await Task.Run(() => + { + // update the player data with the latest update value from the players collection foreach (var playerData in dataContainer.Players) { - token.ThrowIfCancellationRequested(); - await Task.Run(async () => - { - var id = playerData.PlayerId; - if (!string.IsNullOrWhiteSpace(id)) - { - var validPlayer = new PlayerInfo() - { - PlayerId = playerData.PlayerId, - PlayerName = playerData.PlayerName, - IsValid = true, - }; + var id = playerData.PlayerId; + if (string.IsNullOrWhiteSpace(id)) + continue; - this.players.AddOrUpdate(id, validPlayer, (k, v) => { v.PlayerName = playerData.PlayerName; v.IsValid = true; return v; }); + _players.TryGetValue(id, out PlayerInfo player); + player?.UpdatePlatformData(playerData); + } + }, token); + + try + { + // load the player data from steam + await dataContainer.LoadSteamAsync(SteamUtils.SteamWebApiKey, STEAM_UPDATE_INTERVAL); + } + catch (Exception ex) + { + _errorLogger.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: LoadSteamAsync. {ex.Message}\r\n{ex.StackTrace}"); + } + + if (token.IsCancellationRequested) + return; + + foreach (var playerData in dataContainer.Players) + { + if (token.IsCancellationRequested) + return; + + await Task.Run(async () => + { + var id = playerData.PlayerId; + if (string.IsNullOrWhiteSpace(id)) + { + id = Path.GetFileNameWithoutExtension(playerData.Filename); + if (string.IsNullOrWhiteSpace(id)) + { + _debugLogger.Debug($"{nameof(UpdatePlayerDetailsAsync)} - Error: corrupted profile.\r\n{playerData.Filename}."); } else { - id = Path.GetFileNameWithoutExtension(playerData.Filename); - if (string.IsNullOrWhiteSpace(id)) + var invalidPlayer = new PlayerInfo() { - var invalidPlayer = new PlayerInfo() - { - PlayerId = id, - PlayerName = "< corrupted profile >", - IsValid = false, - }; + PlayerId = id, + PlayerName = "< corrupted profile >", + IsValid = false, + }; - this.players.AddOrUpdate(id, invalidPlayer, (k, v) => { v.PlayerName = "< corrupted profile >"; v.IsValid = false; return v; }); - } - else - { - _debugLogger.Debug($"{nameof(UpdatePlayerDetailsAsync)} - Error: corrupted profile.\r\n{playerData.Filename}."); - } + _players.AddOrUpdate(id, invalidPlayer, (k, v) => { v.PlayerName = "< corrupted profile >"; v.IsValid = false; return v; }); } - - if (this.players.TryGetValue(id, out PlayerInfo player) && player != null) + } + else + { + var validPlayer = new PlayerInfo() { - player.UpdateData(playerData); + PlayerId = id, + PlayerName = playerData.PlayerName, + IsValid = true, + }; - await TaskUtils.RunOnUIThreadAsync(() => - { - player.IsAdmin = rconParams?.Server?.Profile?.ServerFilesAdmins?.Any(u => u.PlayerId.Equals(player.PlayerId, StringComparison.OrdinalIgnoreCase)) ?? false; - player.IsWhitelisted = rconParams?.Server?.Profile?.ServerFilesWhitelisted?.Any(u => u.PlayerId.Equals(player.PlayerId, StringComparison.OrdinalIgnoreCase)) ?? false; - }); - } - }, token); - } + _players.AddOrUpdate(id, validPlayer, (k, v) => { v.PlayerName = playerData.PlayerName; v.IsValid = true; return v; }); + } - token.ThrowIfCancellationRequested(); + if (_players.TryGetValue(id, out PlayerInfo player) && player != null) + { + player.UpdateData(playerData); - // remove any players that do not have a player file. - var droppedPlayers = this.players.Values.Where(p => dataContainer.Players.FirstOrDefault(pd => pd.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null).ToArray(); - foreach (var droppedPlayer in droppedPlayers) - { - players.TryRemove(droppedPlayer.PlayerId, out PlayerInfo player); - } + await TaskUtils.RunOnUIThreadAsync(() => + { + player.IsAdmin = _rconParameters?.Server?.Profile?.ServerFilesAdmins?.Any(u => u.PlayerId.Equals(player.PlayerId, StringComparison.OrdinalIgnoreCase)) ?? false; + player.IsWhitelisted = _rconParameters?.Server?.Profile?.ServerFilesWhitelisted?.Any(u => u.PlayerId.Equals(player.PlayerId, StringComparison.OrdinalIgnoreCase)) ?? false; + }); + } + }, token); + } + + if (token.IsCancellationRequested) + return; + + // remove any players that do not have a player file. + var droppedPlayers = _players.Values.Where(p => dataContainer.Players.FirstOrDefault(pd => pd.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null).ToArray(); + foreach (var droppedPlayer in droppedPlayers) + { + _players.TryRemove(droppedPlayer.PlayerId, out PlayerInfo player); } } private void UpdatePlayerCollection() { - lock (updatePlayerCollectionLock) + lock (_updatePlayerCollectionLock) { - this.Players = new SortableObservableCollection(players.Values); + this.Players = new SortableObservableCollection(_players.Values); this.CountPlayers = this.Players.Count; this.CountInvalidPlayers = this.Players.Count(p => !p.IsValid); this.CountOnlinePlayers = this.Players.Count(p => p.IsOnline); diff --git a/src/ARKServerManager/Lib/ServerStatusWatcher.cs b/src/ARKServerManager/Lib/ServerStatusWatcher.cs index f2958d83..238034d3 100644 --- a/src/ARKServerManager/Lib/ServerStatusWatcher.cs +++ b/src/ARKServerManager/Lib/ServerStatusWatcher.cs @@ -310,8 +310,7 @@ namespace ServerManagerTool.Lib try { - var players = server?.GetPlayers()?.Where(p => !string.IsNullOrWhiteSpace(p.Name?.Trim())); - onlinePlayerCount = players?.Count() ?? 0; + onlinePlayerCount = serverInfo?.Players ?? 0; } catch (Exception) { diff --git a/src/ARKServerManager/VersionFeed.xml b/src/ARKServerManager/VersionFeed.xml index 55be8470..00306366 100644 --- a/src/ARKServerManager/VersionFeed.xml +++ b/src/ARKServerManager/VersionFeed.xml @@ -9,10 +9,10 @@ urn:uuid:2C48A585-72D2-43FB-8987-6B5F0B3E460F - 1.1.425 (1.1.425.4) - 1.1.425.4 + 1.1.425 (1.1.425.5) + 1.1.425.5 - 2022-05-07T00:00:00Z + 2022-05-08T00:00:00Z

@@ -24,6 +24,7 @@

  • Auto Backup Settings - added RCON broadcast mode droplist, so backup processes can send messages via RCON using this mode.
  • Global Backup Settings - added option to include/exclude the SaveGames folder in the worldsave backup (default exclude).
  • Global Alert Settings - added new textbox allowing the formatting of the ipaddress and port in the server startup message (default {ipaddress}:{port}).
  • +
  • Rcon Window - minor changes to the player list, online player count and server status.
  • Security Protocol Changes - updated the security protocols to use TLS12 and TLS13.
  • diff --git a/src/ARKServerManager/VersionFeedBeta.xml b/src/ARKServerManager/VersionFeedBeta.xml index eeaf610b..aab4b0a6 100644 --- a/src/ARKServerManager/VersionFeedBeta.xml +++ b/src/ARKServerManager/VersionFeedBeta.xml @@ -5,7 +5,30 @@ Ark Server Manager Version Feed This is the Ark Server Manager beta version feed. - 2022-05-07T00:00:00Z + 2022-05-08T00:00:00Z + + + urn:uuid:CEA21F86-5943-46F9-8807-604695E42A25 + 1.1.425 (1.1.425.5) + 1.1.425.5 + + 2022-05-08T00:00:00Z + +
    +

    + CHANGE +
    +

      +
    • Rcon Window - minor changes to the player list, online player count and server status.
    • +
    +

    +
    +
    + + bletch + bletch1971@hotmail.com + +
    urn:uuid:358E0063-27AE-4D5F-BDA5-BD9723EE353E diff --git a/src/ARKServerManager/Windows/RCONWindow.xaml.cs b/src/ARKServerManager/Windows/RCONWindow.xaml.cs index bb2e8178..50e87f9b 100644 --- a/src/ARKServerManager/Windows/RCONWindow.xaml.cs +++ b/src/ARKServerManager/Windows/RCONWindow.xaml.cs @@ -1,4 +1,5 @@ -using ServerManagerTool.Common.Extensions; +using ServerManagerTool.Common.Enums; +using ServerManagerTool.Common.Extensions; using ServerManagerTool.Common.Lib; using ServerManagerTool.Common.Utils; using ServerManagerTool.Enums; @@ -911,7 +912,7 @@ namespace ServerManagerTool ConsoleContent.Blocks.Add(b); } - private IEnumerable FormatCommandInput(ServerRCON.ConsoleCommand command) + private IEnumerable FormatCommandInput(ConsoleCommand command) { if (command.command.Equals(ServerRCON.RCON_COMMAND_BROADCAST, StringComparison.OrdinalIgnoreCase)) { @@ -928,7 +929,7 @@ namespace ServerManagerTool } } - private IEnumerable FormatCommandOutput(ServerRCON.ConsoleCommand command) + private IEnumerable FormatCommandOutput(ConsoleCommand command) { bool firstLine = true; @@ -1009,13 +1010,13 @@ namespace ServerManagerTool private void RenderConnectionStateChange(DependencyPropertyChangedEventArgs e) { - var oldStatus = (ServerRCON.ConsoleStatus)e.OldValue; - var newStatus = (ServerRCON.ConsoleStatus)e.NewValue; + var oldStatus = (ConsoleStatus)e.OldValue; + var newStatus = (ConsoleStatus)e.NewValue; if(oldStatus != newStatus) { var p = new Paragraph(); - if (newStatus == ServerRCON.ConsoleStatus.Connected) + if (newStatus == ConsoleStatus.Connected) { p.Inlines.Add(new RCONOutput_ConnectionChanged(true)); } @@ -1028,7 +1029,7 @@ namespace ServerManagerTool } } - private void RenderRCONCommandOutput(ServerRCON.ConsoleCommand command) + private void RenderRCONCommandOutput(ConsoleCommand command) { // // Format output diff --git a/src/ConanServerManager/Globalization/en-US/en-US.xaml b/src/ConanServerManager/Globalization/en-US/en-US.xaml index f2cd0595..6d807dc2 100644 --- a/src/ConanServerManager/Globalization/en-US/en-US.xaml +++ b/src/ConanServerManager/Globalization/en-US/en-US.xaml @@ -529,8 +529,8 @@ Disconnected Connected - Player '{0}' joined the game. - Player '{0}' left the game. + '{0}' joined the game. + '{0}' left the game. diff --git a/src/ConanServerManager/Lib/ServerRcon.cs b/src/ConanServerManager/Lib/ServerRcon.cs index 4900b4ca..4e5c4113 100644 --- a/src/ConanServerManager/Lib/ServerRcon.cs +++ b/src/ConanServerManager/Lib/ServerRcon.cs @@ -29,16 +29,16 @@ namespace ServerManagerTool.Lib public const string RCON_COMMAND_BROADCAST = "broadcast"; public const string RCON_COMMAND_ALERT = "alert"; public const string RCON_COMMAND_SERVER = "server"; - public const string RCON_COMMAND_LISTPLAYERS = "#managerplayerlist#"; + public const string RCON_COMMAND_LISTPLAYERS = "listplayers"; public event EventHandler PlayersCollectionUpdated; + public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(nameof(Status), typeof(ConsoleStatus), typeof(ServerRcon), new PropertyMetadata(ConsoleStatus.Disconnected)); public static readonly DependencyProperty PlayersProperty = DependencyProperty.Register(nameof(Players), typeof(SortableObservableCollection), typeof(ServerRcon), new PropertyMetadata(null)); public static readonly DependencyProperty CountPlayersProperty = DependencyProperty.Register(nameof(CountPlayers), typeof(int), typeof(ServerRcon), new PropertyMetadata(0)); public static readonly DependencyProperty CountInvalidPlayersProperty = DependencyProperty.Register(nameof(CountInvalidPlayers), typeof(int), typeof(ServerRcon), new PropertyMetadata(0)); public static readonly DependencyProperty CountOnlinePlayersProperty = DependencyProperty.Register(nameof(CountOnlinePlayers), typeof(int), typeof(ServerRcon), new PropertyMetadata(0)); - private static readonly ConcurrentDictionary locks = new ConcurrentDictionary(); private static readonly char[] lineSplitChars = new char[] { '\n' }; private static readonly char[] argsSplitChars = new char[] { ' ' }; @@ -48,7 +48,7 @@ namespace ServerManagerTool.Lib private RconParameters _rconParameters; private QueryMaster.Rcon _console; - private int maxCommandRetries = 3; + private int _maxCommandRetries = 3; private readonly ConcurrentDictionary _players = new ConcurrentDictionary(); private readonly object _updatePlayerCollectionLock = new object(); @@ -63,16 +63,15 @@ namespace ServerManagerTool.Lib public ServerRcon(RconParameters parameters) { - this._rconParameters = parameters; + _rconParameters = parameters; + + _allLogger = App.GetProfileLogger(_rconParameters.ProfileId, "Rcon_All", LogLevel.Info, LogLevel.Info); + _chatLogger = App.GetProfileLogger(_rconParameters.ProfileId, "Rcon_Chat", LogLevel.Info, LogLevel.Info); + _eventLogger = App.GetProfileLogger(_rconParameters.ProfileId, "Rcon_Event", LogLevel.Info, LogLevel.Info); + _debugLogger = App.GetProfileLogger(_rconParameters.ProfileId, "Rcon_Debug", LogLevel.Trace, LogLevel.Debug); + _errorLogger = App.GetProfileLogger(_rconParameters.ProfileId, "Rcon_Error", LogLevel.Error, LogLevel.Fatal); + this.Players = new SortableObservableCollection(); - - _allLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_All", LogLevel.Info, LogLevel.Info); - _chatLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Chat", LogLevel.Info, LogLevel.Info); - _eventLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Event", LogLevel.Info, LogLevel.Info); - _debugLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Debug", LogLevel.Trace, LogLevel.Debug); - _errorLogger = App.GetProfileLogger(this._rconParameters.ProfileId, "Rcon_Error", LogLevel.Error, LogLevel.Fatal); - - UpdatePlayersAsync().DoNotWait(); } public async Task DisposeAsync() @@ -81,18 +80,25 @@ namespace ServerManagerTool.Lib { _cancellationTokenSource.Cancel(); } - await this._commandProcessor.DisposeAsync(); - await this._outputProcessor.DisposeAsync(); - for (int index = this._commandListeners.Count - 1; index >= 0; index--) + await _commandProcessor.DisposeAsync(); + await _outputProcessor.DisposeAsync(); + + for (int index = _commandListeners.Count - 1; index >= 0; index--) { - this._commandListeners[index].Dispose(); + _commandListeners[index].Dispose(); } _disposed = true; } #region Properties + public ConsoleStatus Status + { + get { return (ConsoleStatus)GetValue(StatusProperty); } + set { SetValue(StatusProperty, value); } + } + public SortableObservableCollection Players { get { return (SortableObservableCollection)GetValue(PlayersProperty); } @@ -119,6 +125,12 @@ namespace ServerManagerTool.Lib #endregion #region Methods + public void Initialize() + { + _commandProcessor.PostAction(AutoPlayerList); + UpdatePlayersAsync().DoNotWait(); + } + private void LogEvent(LogEventType eventType, string message) { switch (eventType) @@ -144,32 +156,46 @@ namespace ServerManagerTool.Lib private bool Reconnect() { - if (this._console != null) + if (_console != null) { - this._console.Dispose(); - this._console = null; + _console.Dispose(); + _console = null; } - var endpoint = new IPEndPoint(this._rconParameters.RconHostIP, this._rconParameters.RconPort); + var endpoint = new IPEndPoint(_rconParameters.RconHostIP, _rconParameters.RconPort); var server = QueryMaster.ServerQuery.GetServerInstance(QueryMaster.EngineType.Source, endpoint, sendTimeOut: 10000, receiveTimeOut: 10000); - this._console = server.GetControl(this._rconParameters.RconPassword); - return this._console != null; + _console = server.GetControl(_rconParameters.RconPassword); + return _console != null; } public IDisposable RegisterCommandListener(Action callback) { var listener = new CommandListener { Callback = callback, DisposeAction = UnregisterCommandListener }; - this._commandListeners.Add(listener); + _commandListeners.Add(listener); return listener; } private void UnregisterCommandListener(CommandListener listener) { - this._commandListeners.Remove(listener); + _commandListeners.Remove(listener); } #endregion #region Process Methods + private Task AutoPlayerList() + { + return _commandProcessor.PostAction(() => + { + ProcessInput(new ConsoleCommand() { rawCommand = RCON_COMMAND_LISTPLAYERS, suppressCommand = true, suppressOutput = true }); + Task.Delay(PLAYER_LIST_INTERVAL).ContinueWith(t => _commandProcessor.PostAction(AutoPlayerList)).DoNotWait(); + }); + } + + public Task IssueCommand(string userCommand) + { + return _commandProcessor.PostAction(() => ProcessInput(new ConsoleCommand() { rawCommand = userCommand })); + } + private bool ProcessInput(ConsoleCommand command) { try @@ -214,14 +240,14 @@ namespace ServerManagerTool.Lib command.status = ConsoleStatus.Connected; - this._outputProcessor.PostAction(() => ProcessOutput(command)); + _outputProcessor.PostAction(() => ProcessOutput(command)); return true; } catch (Exception ex) { _errorLogger?.Error($"Failed to send command '{command.rawCommand}'. {ex.Message}"); command.status = ConsoleStatus.Disconnected; - this._outputProcessor.PostAction(() => ProcessOutput(command)); + _outputProcessor.PostAction(() => ProcessOutput(command)); return false; } } @@ -236,11 +262,6 @@ namespace ServerManagerTool.Lib NotifyCommand(command); } - public Task IssueCommand(string userCommand) - { - return this._commandProcessor.PostAction(() => ProcessInput(new ConsoleCommand() { rawCommand = userCommand })); - } - // This is bound to the UI thread private void HandleCommand(ConsoleCommand command) { @@ -249,8 +270,8 @@ namespace ServerManagerTool.Lib // if (command?.command?.Equals(RCON_COMMAND_LISTPLAYERS, StringComparison.OrdinalIgnoreCase) ?? false) { - command.suppressCommand = true; - command.suppressOutput = false; + command.lines = HandleListPlayersCommand(command.lines); + command.suppressOutput = command.lines.Count() == 0; } if (command?.command?.Equals(RCON_COMMAND_BROADCAST, StringComparison.OrdinalIgnoreCase) ?? false) @@ -272,6 +293,87 @@ namespace ServerManagerTool.Lib } } + // This is bound to the UI thread + private List HandleListPlayersCommand(IEnumerable commandLines) + { + var output = new List(); + var onlinePlayers = new List(); + + var playerLines = commandLines?.ToList() ?? new List(); + if (playerLines.Count > 1) + { + // remove the first line, as it is a header row + playerLines.RemoveAt(0); + + foreach (var line in playerLines) + { + var elements = line.Split('|'); + if (elements.Length != 6) + // Invalid data. Ignore it. + continue; + + var id = elements[3]?.Trim(); + + if (onlinePlayers.FirstOrDefault(p => p.PlayerId.Equals(id, StringComparison.OrdinalIgnoreCase)) != null) + // Duplicate data. Ignore it. + continue; + + var newPlayer = new PlayerInfo() + { + PlayerId = id, + PlayerName = elements[2].Substring(0, elements[2].IndexOf('#')).Trim(), + CharacterName = elements[1].Trim(), + IsOnline = true, + }; + onlinePlayers.Add(newPlayer); + + var playerJoined = false; + _players.AddOrUpdate(newPlayer.PlayerId, + (k) => + { + playerJoined = true; + return newPlayer; + }, + (k, v) => + { + playerJoined = !v.IsOnline; + v.PlayerName = newPlayer.PlayerName; + v.IsOnline = newPlayer.IsOnline; + return v; + } + ); + + if (playerJoined) + { + var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Join") ?? "'{0}' joined the game."; + var message = string.Format(messageFormat, newPlayer.CharacterName); + output.Add(message); + LogEvent(LogEventType.Event, message); + LogEvent(LogEventType.All, message); + } + } + } + + var droppedPlayers = _players.Values.Where(p => onlinePlayers.FirstOrDefault(np => np.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null); + foreach (var droppedPlayer in droppedPlayers) + { + if (droppedPlayer.IsOnline) + { + droppedPlayer.IsOnline = false; + + var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Leave") ?? "'{0}' left the game."; + var message = string.Format(messageFormat, droppedPlayer.CharacterName); + output.Add(message); + LogEvent(LogEventType.Event, message); + LogEvent(LogEventType.All, message); + } + } + + UpdatePlayerCollection(); + + return output; + } + // This is bound to the UI thread private void NotifyCommand(ConsoleCommand command) { @@ -295,13 +397,13 @@ namespace ServerManagerTool.Lib Exception lastException = null; int retries = 0; - while (retries < maxCommandRetries) + while (retries < _maxCommandRetries) { - if (this._console != null) + if (_console != null) { try { - return this._console.SendCommand(command); + return _console.SendCommand(command); } catch (Exception ex) { @@ -324,32 +426,25 @@ namespace ServerManagerTool.Lib retries++; } - this.maxCommandRetries = 10; - _errorLogger?.Error($"Failed to connect to Rcon at {this._rconParameters.RconHostIP}:{this._rconParameters.RconPort} with {this._rconParameters.RconPassword}. {lastException.Message}"); - throw new Exception($"Command failed to send after {maxCommandRetries} attempts. Last exception: {lastException.Message}", lastException); + _maxCommandRetries = 10; + _errorLogger?.Error($"Failed to connect to Rcon at {_rconParameters.RconHostIP}:{_rconParameters.RconPort} with {_rconParameters.RconPassword}. {lastException.Message}"); + throw new Exception($"Command failed to send after {_maxCommandRetries} attempts. Last exception: {lastException.Message}", lastException); } #endregion private async Task UpdatePlayersAsync() { - if (this._disposed) + if (_disposed) return; _cancellationTokenSource = new CancellationTokenSource(); - var token = _cancellationTokenSource.Token; - var output = await UpdatePlayerDetailsAsync(_cancellationTokenSource.Token); + await UpdatePlayerDetailsAsync(_cancellationTokenSource.Token); var cancelled = _cancellationTokenSource.IsCancellationRequested; if (!cancelled) { - await TaskUtils.RunOnUIThreadAsync(() => - { - UpdatePlayerCollection(); - - var command = new ConsoleCommand() { rawCommand = RCON_COMMAND_LISTPLAYERS, command = RCON_COMMAND_LISTPLAYERS, lines = output }; - ProcessOutput(command); - }); + await TaskUtils.RunOnUIThreadAsync(() => UpdatePlayerCollection()); } _cancellationTokenSource.Dispose(); @@ -357,19 +452,19 @@ namespace ServerManagerTool.Lib if (!cancelled) { - await Task.Delay(PLAYER_LIST_INTERVAL).ContinueWith(t => UpdatePlayersAsync()); + await Task.Delay(PLAYER_LIST_INTERVAL) + .ContinueWith(t => UpdatePlayersAsync()); } } - private async Task> UpdatePlayerDetailsAsync(CancellationToken token) + private async Task UpdatePlayerDetailsAsync(CancellationToken token) { - if (this._disposed) - return new List(); + if (_disposed) + return; if (string.IsNullOrWhiteSpace(_rconParameters.GameFile) || !File.Exists(_rconParameters.GameFile)) - return new List(); + return; - var savedPath = ServerProfile.GetProfileSavePath(_rconParameters.InstallDirectory); DataContainer dataContainer = null; try @@ -379,12 +474,12 @@ namespace ServerManagerTool.Lib } catch (Exception ex) { - _errorLogger?.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: CreateAsync. {ex.Message}\r\n{ex.StackTrace}"); - return new List(); + _errorLogger.Error($"{nameof(UpdatePlayerDetailsAsync)} - Error: CreateAsync. {ex.Message}\r\n{ex.StackTrace}"); + return; } if (token.IsCancellationRequested) - return new List(); + return; await Task.Run(() => { @@ -395,61 +490,41 @@ namespace ServerManagerTool.Lib if (string.IsNullOrWhiteSpace(id)) continue; - this._players.TryGetValue(id, out PlayerInfo player); + _players.TryGetValue(id, out PlayerInfo player); player?.UpdatePlatformData(playerData); } }, token); if (token.IsCancellationRequested) - return new List(); - - var totalPlayers = dataContainer.Players.Count; - var output = new List(); + return; foreach (var playerData in dataContainer.Players) { - token.ThrowIfCancellationRequested(); + if (token.IsCancellationRequested) + return; + await Task.Run(async () => { var id = playerData.PlayerId; - if (!string.IsNullOrWhiteSpace(id)) + if (string.IsNullOrWhiteSpace(id)) + { + _debugLogger?.Debug($"{nameof(UpdatePlayerDetailsAsync)} - Error: corrupted profile.\r\n{playerData.CharacterId}."); + } + else { var validPlayer = new PlayerInfo() { PlayerId = id, PlayerName = playerData.PlayerName, + CharacterName = playerData.CharacterName, IsValid = true, }; - this._players.AddOrUpdate(id, validPlayer, (k, v) => { v.PlayerName = playerData.PlayerName; v.IsValid = true; return v; }); - } - else - { - _debugLogger?.Debug($"{nameof(UpdatePlayerDetailsAsync)} - Error: corrupted profile.\r\n{playerData.CharacterId}."); + _players.AddOrUpdate(id, validPlayer, (k, v) => { v.PlayerName = playerData.PlayerName; v.CharacterName = playerData.CharacterName; v.IsValid = true; return v; }); } - if (this._players.TryGetValue(id, out PlayerInfo player) && player != null) + if (_players.TryGetValue(id, out PlayerInfo player) && player != null) { - if (player.IsOnline != playerData.Online) - { - if (playerData.Online) - { - var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Join") ?? "Player '{0}' joined the game."; - var message = string.Format(messageFormat, playerData.CharacterName); - output.Add(message); - LogEvent(LogEventType.Event, message); - LogEvent(LogEventType.All, message); - } - else - { - var messageFormat = GlobalizedApplication.Instance.GetResourceString("Player_Leave") ?? "Player '{0}' left the game."; - var message = string.Format(messageFormat, playerData.CharacterName); - output.Add(message); - LogEvent(LogEventType.Event, message); - LogEvent(LogEventType.All, message); - } - } - player.UpdateData(playerData); await TaskUtils.RunOnUIThreadAsync(() => @@ -461,16 +536,14 @@ namespace ServerManagerTool.Lib } if (token.IsCancellationRequested) - return new List(); + return; - // remove any players that do not have a player record. - var droppedPlayers = this._players.Values.Where(p => dataContainer.Players.FirstOrDefault(pd => pd.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null).ToArray(); + // remove any players that do not have a player file. + var droppedPlayers = _players.Values.Where(p => dataContainer.Players.FirstOrDefault(pd => pd.PlayerId.Equals(p.PlayerId, StringComparison.OrdinalIgnoreCase)) == null).ToArray(); foreach (var droppedPlayer in droppedPlayers) { _players.TryRemove(droppedPlayer.PlayerId, out PlayerInfo player); } - - return output; } private void UpdatePlayerCollection() diff --git a/src/ConanServerManager/Lib/ServerStatusWatcher.cs b/src/ConanServerManager/Lib/ServerStatusWatcher.cs index a3465c1e..cb1894a3 100644 --- a/src/ConanServerManager/Lib/ServerStatusWatcher.cs +++ b/src/ConanServerManager/Lib/ServerStatusWatcher.cs @@ -311,8 +311,7 @@ namespace ServerManagerTool.Lib try { - // load the player data from the files. - onlinePlayerCount = DataContainer.GetOnlinePlayerCount(gameFile); + onlinePlayerCount = serverInfo?.Players ?? 0; } catch (Exception) { diff --git a/src/ConanServerManager/Lib/ViewModel/PlayerInfo.cs b/src/ConanServerManager/Lib/ViewModel/PlayerInfo.cs index 87469136..c1d4f4cb 100644 --- a/src/ConanServerManager/Lib/ViewModel/PlayerInfo.cs +++ b/src/ConanServerManager/Lib/ViewModel/PlayerInfo.cs @@ -115,7 +115,7 @@ namespace ServerManagerTool.Lib.ViewModel this.CharacterId = playerData?.CharacterId ?? 0L; this.CharacterName = playerData?.CharacterName; this.GuildName = playerData?.Guild?.GuildName; - this.IsOnline = playerData?.Online ?? false; + //this.IsOnline = playerData?.Online ?? false; this.LastOnline = playerData?.LastOnline; } diff --git a/src/ConanServerManager/VersionFeed.xml b/src/ConanServerManager/VersionFeed.xml index 11823158..cb59dd8b 100644 --- a/src/ConanServerManager/VersionFeed.xml +++ b/src/ConanServerManager/VersionFeed.xml @@ -5,14 +5,14 @@ Conan Server Manager Version Feed This is the Conan Server Manager release version feed. - 2022-05-06T00:00:00Z + 2022-05-08T00:00:00Z urn:uuid:AD8ABBB5-093A-4FDB-B473-FCED2DB46781 - 1.1.69 (1.1.69.4) - 1.1.69.4 + 1.1.69 (1.1.69.5) + 1.1.69.5 - 2022-05-07T00:00:00Z + 2022-05-08T00:00:00Z

    @@ -27,6 +27,8 @@

  • Auto Backup Settings - added RCON broadcast mode droplist, so backup processes can send messages via RCON using this mode.
  • Global Backup Settings - added option to include/exclude the SaveGames folder in the worldsave backup (default exclude).
  • Global Alert Settings - added new textbox allowing the formatting of the ipaddress and port in the server startup message (default {ipaddress}:{port}).
  • +
  • Rcon Window - minor changes to the player list, online player count and server status.
  • +
  • Online Player Count - fixed the online player count to display the correct value.
  • Security Protocol Changes - updated the security protocols to use TLS12 and TLS13.
  • diff --git a/src/ConanServerManager/VersionFeedBeta.xml b/src/ConanServerManager/VersionFeedBeta.xml index 42427df4..36cf964d 100644 --- a/src/ConanServerManager/VersionFeedBeta.xml +++ b/src/ConanServerManager/VersionFeedBeta.xml @@ -5,7 +5,31 @@ Conan Server Manager Version Feed This is the Conan Server Manager beta version feed. - 2022-05-07T00:00:00Z + 2022-05-08T00:00:00Z + + + urn:uuid:68ECEA73-1C9C-4DCB-807B-0D812E986993 + 1.1.69 (1.1.69.5) + 1.1.69.5 + + 2022-05-08T00:00:00Z + +
    +

    + CHANGE +
    +

      +
    • Rcon Window - minor changes to the player list, online player count and server status.
    • +
    • Online Player Count - fixed the online player count to display the correct value.
    • +
    +

    +
    +
    + + bletch + bletch1971@hotmail.com + +
    urn:uuid:D886601C-8260-4E06-8B2A-D8F77F1F1B58 diff --git a/src/ConanServerManager/Windows/RconWindow.xaml.cs b/src/ConanServerManager/Windows/RconWindow.xaml.cs index 69ea5cff..6599b251 100644 --- a/src/ConanServerManager/Windows/RconWindow.xaml.cs +++ b/src/ConanServerManager/Windows/RconWindow.xaml.cs @@ -206,6 +206,8 @@ namespace ServerManagerTool this.MaxPlayerLabel.Visibility = Visibility.Collapsed; } + this.ServerRcon.Initialize(); + this.DataContext = this; RenderCommentsBlock( @@ -867,12 +869,9 @@ namespace ServerManagerTool } } - if (!(command.suppressCommand && command.suppressOutput)) + if (p.Inlines.Count > 0) { - if (p.Inlines.Count > 0) - { - AddBlockContent(p); - } + AddBlockContent(p); } } #endregion