From c865cb00ea2658acabe6e6bd656dcb6b19ff6c57 Mon Sep 17 00:00:00 2001 From: Anton Tarasenko Date: Wed, 12 Jan 2022 02:14:46 +0700 Subject: [PATCH] Add original name tracking to `ConnectionService` --- .../Unreal/Connections/ConnectionService.uc | 107 +++++++++++++++++- .../Connections/MutatorListener_Connection.uc | 52 --------- 2 files changed, 103 insertions(+), 56 deletions(-) delete mode 100644 sources/Unreal/Connections/MutatorListener_Connection.uc diff --git a/sources/Unreal/Connections/ConnectionService.uc b/sources/Unreal/Connections/ConnectionService.uc index e41c9ea..f87edd9 100644 --- a/sources/Unreal/Connections/ConnectionService.uc +++ b/sources/Unreal/Connections/ConnectionService.uc @@ -25,6 +25,8 @@ class ConnectionService extends Service; struct Connection { var public PlayerController controllerReference; + // Native code will change player's name, so lets store the original value + var public string originalName; // Remember these for the time `controllerReference` dies // and becomes `none`. var public string networkAddress; @@ -36,6 +38,28 @@ struct Connection var private array activeConnections; +// We consider connection created once appropriate +// `KFSteamStatsAndAchievements` spawns, however `PlayerController` usually +// spawns a tick earlier and that is the moment when some of the information +// needs to be obtained. +// To later re-associate it with `Connection` structure we temporarily +// store it by pairing with `PlayerController`. +struct PendingConnection +{ + var private PlayerController controllerReference; + var private string originalName; + // `KFSteamStatsAndAchievements` should be created a tick after + // `PlayerController` reference, so we can discard any `PendingConnection`s + // that have lasted too long without matching a `Connection`. + var private int ticksPassed; +}; +var private array pendingConnections; +// We record the name, given to us in the options from `OnModifyLogin` event +// in this variable. Since corresponding `PlayerController` is created inside +// `Login()` function right after that - it is easy to associate. +var private string lastNickNameFromModifyLogin; +var private const int PENDING_CONNECTION_LIFETIME; + var private Connection_Signal onConnectionEstablishedSignal; var private Connection_Signal onConnectionLostSignal; @@ -73,7 +97,10 @@ protected function OnLaunch() { local Controller nextController; local PlayerController nextPlayerController; - _.unreal.mutator.OnCheckReplacement(_self).connect = TryAddingController; + _.unreal.mutator.OnModifyLogin(_self).connect = RememberLoginOptions; + _.unreal.mutator.OnCheckReplacement(_self).connect = TryAddingController; + _.unreal.mutator.OnCheckReplacement(_self).connect = + RecordPendingInformation; onConnectionEstablishedSignal = Connection_Signal(_.memory.Allocate(class'Connection_Signal')); onConnectionLostSignal = @@ -93,6 +120,8 @@ protected function OnLaunch() protected function OnShutdown() { default.activeConnections = activeConnections; + _.unreal.mutator.OnModifyLogin(_self).Disconnect(); + _.unreal.mutator.OnCheckReplacement(_self).Disconnect(); _.memory.Free(onConnectionEstablishedSignal); _.memory.Free(onConnectionLostSignal); onConnectionEstablishedSignal = none; @@ -135,7 +164,6 @@ private function int GetConnectionIndex(PlayerController controllerToCheck) private function RemoveBrokenConnections() { local int i; - i = 0; while (i < activeConnections.length) { if (activeConnections[i].controllerReference == none) @@ -150,6 +178,20 @@ private function RemoveBrokenConnections() i += 1; } } + // Silently remove outdated pending connection + i = 0; + while (i < pendingConnections.length) + { + pendingConnections[i].ticksPassed += 1; + if ( pendingConnections[i].controllerReference == none + || pendingConnections[i].ticksPassed > PENDING_CONNECTION_LIFETIME) + { + pendingConnections.Remove(i, 1); + } + else { + i += 1; + } + } } /** @@ -185,13 +227,21 @@ public final function Connection GetConnection(PlayerController player) */ public final function bool RegisterConnection(PlayerController player) { - local Connection newConnection; + local int pendingIndex; + local Connection newConnection; if (!IsHumanController(player)) return false; if (GetConnectionIndex(player) >= 0) return true; newConnection.controllerReference = player; newConnection.idHash = player.GetPlayerIDHash(); newConnection.networkAddress = player.GetPlayerNetworkAddress(); + pendingIndex = GetPendingConnectionIndex(player); + if (pendingIndex >= 0) + { + newConnection.originalName = + pendingConnections[pendingIndex].originalName; + pendingConnections.Remove(pendingIndex, 1); + } activeConnections[activeConnections.length] = newConnection; // Remember recorded connections in case someone decides to // nuke this service @@ -224,7 +274,30 @@ public final function array GetActiveConnections( return activeConnections; } -function bool TryAddingController(Actor other, out byte isSuperRelevant) +private function RememberLoginOptions(out string portal, out string options) +{ + lastNickNameFromModifyLogin = + _.unreal.GetGameType().ParseOption(options, "Name"); +} + +private function bool RecordPendingInformation( + Actor other, + out byte isSuperRelevant) +{ + local int pendingIndex; + local PlayerController newPlayerController; + newPlayerController = PlayerController(other); + if (newPlayerController != none) + { + pendingIndex = GetPendingConnectionIndex(newPlayerController); + pendingConnections[pendingIndex].originalName = + lastNickNameFromModifyLogin; + lastNickNameFromModifyLogin = ""; + } + return true; +} + +private function bool TryAddingController(Actor other, out byte isSuperRelevant) { // We are looking for `KFSteamStatsAndAchievements` instead of // `PlayerController` because, by the time they it's created, @@ -244,6 +317,28 @@ function bool TryAddingController(Actor other, out byte isSuperRelevant) return true; } +// Returns valid index (creating a record, if necessary) in +// `pendingConnections`for any non-`none` reference. +// Otherwise returns `-1`. +private function int GetPendingConnectionIndex(PlayerController controller) +{ + local int index; + local PendingConnection newRecord; + if (controller == none) { + return -1; + } + for (index = 0; index < pendingConnections.length; index += 1) + { + if (pendingConnections[index].controllerReference == controller) { + return index; + } + } + index = pendingConnections.length; + newRecord.controllerReference = controller; + pendingConnections[index] = newRecord; + return index; +} + // Check if connections are still active every tick. // Should not take any noticeable time when no players are disconnecting. event Tick(float delta) @@ -253,4 +348,8 @@ event Tick(float delta) defaultproperties { + // Pending connection should not live more than one tick, + // but give it more time just in case; + // It will not cause any issues regardless. + PENDING_CONNECTION_LIFETIME = 5 } \ No newline at end of file diff --git a/sources/Unreal/Connections/MutatorListener_Connection.uc b/sources/Unreal/Connections/MutatorListener_Connection.uc deleted file mode 100644 index 88ff02b..0000000 --- a/sources/Unreal/Connections/MutatorListener_Connection.uc +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Overloaded mutator events listener to catch connecting players. - * Copyright 2019 Anton Tarasenko - *------------------------------------------------------------------------------ - * This file is part of Acedia. - * - * Acedia is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3 of the License, or - * (at your option) any later version. - * - * Acedia is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Acedia. If not, see . - */ -class MutatorListener_Connection extends Object - abstract; - -static function bool CheckReplacement(Actor other, out byte isSuperRelevant) -{ - local KFSteamStatsAndAchievements playerSteamStatsAndAchievements; - local PlayerController player; - local ConnectionService service; - // We are looking for `KFSteamStatsAndAchievements` instead of - // `PlayerController` because, by the time they it's created, - // controller should have a valid reference to `PlayerReplicationInfo`, - // as well as valid network address and IDHash (steam id). - // However, neither of those are properly initialized at the point when - // `CheckReplacement` is called for `PlayerController`. - // - // Since `KFSteamStatsAndAchievements` - // is created soon after (at the same tick) - // for each new `PlayerController`, - // we will be detecting new users right after server - // detected and properly initialized them. - playerSteamStatsAndAchievements = KFSteamStatsAndAchievements(other); - if (playerSteamStatsAndAchievements == none) return true; - service = ConnectionService(class'ConnectionService'.static.GetInstance()); - if (service == none) return true; - - player = PlayerController(playerSteamStatsAndAchievements.owner); - service.RegisterConnection(player); - return true; -} - -defaultproperties -{ -} \ No newline at end of file