diff --git a/sources/Global.uc b/sources/Global.uc index 01849ea..f04d72a 100644 --- a/sources/Global.uc +++ b/sources/Global.uc @@ -28,6 +28,7 @@ var public TextAPI text; var public MemoryAPI memory; var public ConsoleAPI console; var public ColorAPI color; +var public UserAPI users; // TODO: APIs must be `remoteRole = ROLE_None` protected function OnCreated() @@ -46,4 +47,6 @@ protected function OnCreated() console = ConsoleAPI(class'ConsoleAPI'.static.GetInstance()); Spawn(class'ColorAPI'); color = ColorAPI(class'ColorAPI'.static.GetInstance()); + Spawn(class'UserAPI'); + users = UserAPI(class'UserAPI'.static.GetInstance()); } \ No newline at end of file diff --git a/sources/Manifest.uc b/sources/Manifest.uc index c60fa1e..2b0b1be 100644 --- a/sources/Manifest.uc +++ b/sources/Manifest.uc @@ -31,5 +31,5 @@ defaultproperties testCases(3) = class'TEST_Text' testCases(4) = class'TEST_TextAPI' testCases(5) = class'TEST_Parser' - testCases(6) = class'TEST_Player' + testCases(6) = class'TEST_User' } \ No newline at end of file diff --git a/sources/Players/APlayer.uc b/sources/Players/APlayer.uc index 53a0360..f98a38e 100644 --- a/sources/Players/APlayer.uc +++ b/sources/Players/APlayer.uc @@ -1,7 +1,9 @@ /** - * Main and only Acedia mutator used for loading Acedia packages - * and providing access to mutator events' calls. - * Name is chosen to make config files more readable. + * Objects of this class are meant to represent a "server user": + * not a particular `PlayerController`, but an entity that server would + * recognize to be the same person even after reconnections. + * It is supposed to store and recognize various stats and + * server privileges. * Copyright 2020 Anton Tarasenko *------------------------------------------------------------------------------ * This file is part of Acedia. @@ -19,8 +21,41 @@ * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ -class APlayer extends AcediaObject; +class APlayer extends AcediaActor; + +var private User identity; +var private PlayerController ownerController; + +// Shortcut to `ConnectionEvents`, so that we don't have to write +// `class'ConnectionEvents'` every time. +var const class events; + +public final function Initialize(PlayerController initOwnerController) +{ + ownerController = initOwnerController; + identity = _.users.FetchByIDHash(initOwnerController.GetPlayerIDHash()); + events.static.CallPlayerConnected(self); +} + +public final function PlayerController GetController() +{ + return ownerController; +} + +public final function Update() +{ + if (ownerController == none) { + events.static.CallPlayerDisconnected(self); + Destroy(); + } +} + +event Tick(float delta) +{ + Update(); +} defaultproperties { + events = class'PlayerEvents' } \ No newline at end of file diff --git a/sources/Players/ConnectionListener_Player.uc b/sources/Players/ConnectionListener_Player.uc new file mode 100644 index 0000000..0837192 --- /dev/null +++ b/sources/Players/ConnectionListener_Player.uc @@ -0,0 +1,52 @@ +/** + * Listener for events generated by 'ConnectionService'. + * 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 ConnectionListener_Player extends ConnectionListenerBase; + +// `PlayerConnected` is called the moment we detect a new player on a server. +static function ConnectionEstablished(ConnectionService.Connection connection) +{ + local PlayerService service; + service = PlayerService(class'PlayerService'.static.Require()); + if (service == none) { + _().logger.Fatal("Cannot start `PlayerService` service" + @ "Acedia will not properly work from now on."); + return; + } + service.RegisterPlayer(connection.controllerReference); +} + +// `PlayerDisconnected` is called the moment we +// detect a player leaving the server. +static function ConnectionLost(ConnectionService.Connection connection) +{ + local PlayerService service; + service = PlayerService(class'PlayerService'.static.Require()); + if (service == none) { + _().logger.Fatal("Cannot start `PlayerService` service" + @ "Acedia will not properly work from now on."); + return; + } + service.UpdateAllPlayers(); +} + +defaultproperties +{ + relatedEvents = class'ConnectionEvents' +} \ No newline at end of file diff --git a/sources/Players/PlayerEvents.uc b/sources/Players/PlayerEvents.uc new file mode 100644 index 0000000..14f2be7 --- /dev/null +++ b/sources/Players/PlayerEvents.uc @@ -0,0 +1,51 @@ +/** + * Event generator for 'ConnectionService'. + * 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 PlayerEvents extends Events + abstract; + +static function CallPlayerConnected(APlayer newPlayer) +{ + local int i; + local array< class > listeners; + listeners = GetListeners(); + for (i = 0; i < listeners.length; i += 1) + { + class(listeners[i]) + .static.PlayerConnected(newPlayer); + } +} + +static function CallPlayerDisconnected(APlayer lostPlayer) +{ + local int i; + local array< class > listeners; + listeners = GetListeners(); + for (i = 0; i < listeners.length; i += 1) + { + class(listeners[i]) + .static.PlayerDisconnected(lostPlayer); + } +} + +defaultproperties +{ + relatedListener = class'PlayerListenerBase' + connectedServiceClass = class'PlayerService' +} \ No newline at end of file diff --git a/sources/Players/PlayerListenerBase.uc b/sources/Players/PlayerListenerBase.uc new file mode 100644 index 0000000..d95b559 --- /dev/null +++ b/sources/Players/PlayerListenerBase.uc @@ -0,0 +1,33 @@ +/** + * Listener for events generated by 'ConnectionService'. + * 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 PlayerListenerBase extends Listener + abstract; + +// `PlayerConnected` is called the moment we detect a new player on a server. +static function PlayerConnected(APlayer newPlayer); + +// `PlayerDisconnected` is called the moment we +// detect a player leaving the server. +static function PlayerDisconnected(APlayer lostPlayer); + +defaultproperties +{ + relatedEvents = class'PlayerEvents' +} \ No newline at end of file diff --git a/sources/Players/PlayerService.uc b/sources/Players/PlayerService.uc new file mode 100644 index 0000000..f99dca3 --- /dev/null +++ b/sources/Players/PlayerService.uc @@ -0,0 +1,86 @@ +/** + * Service for tracking currently connected players. + * Besides simply storing all players it also separately stores (caches) + * players belonging to specific groups to make appropriate getters faster. + * Copyright 2020 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 PlayerService extends Service; + +var private array allPlayers; + +private final function RemoveNonePlayers() +{ + local int i; + while (i < allPlayers.length) + { + if (allPlayers[i] == none) { + allPlayers.Remove(i, 1); + } + else { + i += 1; + } + } +} + +public final function bool RegisterPlayer(PlayerController newPlayerController) +{ + local int i; + local APlayer newPlayer; + if (newPlayerController == none) return false; + + RemoveNonePlayers(); + for (i = 0; i < allPlayers.length; i += 1) + { + if (allPlayers[i] == none) continue; + if (allPlayers[i].GetController() == newPlayerController) { + return false; + } + } + newPlayer = APlayer(_.memory.Allocate(class'APlayer')); + if (newPlayer == none) + { + _.logger.Fatal("Cannot spawn a new instance of `APlayer`." + @ "Acedia will not properly work from now on."); + return false; + } + newPlayer.Initialize(newPlayerController); + allPlayers[allPlayers.length] = newPlayer; + return true; +} + +public final function array GetAllPlayers() +{ + RemoveNonePlayers(); + return allPlayers; +} + +public final function UpdateAllPlayers() +{ + local int i; + RemoveNonePlayers(); + for (i = 0; i < allPlayers.length; i += 1) + { + if (allPlayers[i] != none){ + allPlayers[i].Update(); + } + } +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Services/Connection/ConnectionEvents.uc b/sources/Services/Connection/ConnectionEvents.uc index aad9d42..5adcb0d 100644 --- a/sources/Services/Connection/ConnectionEvents.uc +++ b/sources/Services/Connection/ConnectionEvents.uc @@ -21,7 +21,8 @@ class ConnectionEvents extends Events dependson(ConnectionService) abstract; -static function CallPlayerConnected(ConnectionService.Connection connection) +static function CallConnectionEstablished( + ConnectionService.Connection connection) { local int i; local array< class > listeners; @@ -29,11 +30,11 @@ static function CallPlayerConnected(ConnectionService.Connection connection) for (i = 0; i < listeners.length; i += 1) { class(listeners[i]) - .static.PlayerConnected(connection); + .static.ConnectionEstablished(connection); } } -static function CallPlayerDisconnected(ConnectionService.Connection connection) +static function CallConnectionLost(ConnectionService.Connection connection) { local int i; local array< class > listeners; @@ -41,7 +42,7 @@ static function CallPlayerDisconnected(ConnectionService.Connection connection) for (i = 0; i < listeners.length; i += 1) { class(listeners[i]) - .static.PlayerDisconnected(connection); + .static.ConnectionLost(connection); } } diff --git a/sources/Services/Connection/ConnectionListenerBase.uc b/sources/Services/Connection/ConnectionListenerBase.uc index 204f2fc..beb77ff 100644 --- a/sources/Services/Connection/ConnectionListenerBase.uc +++ b/sources/Services/Connection/ConnectionListenerBase.uc @@ -22,11 +22,11 @@ class ConnectionListenerBase extends Listener abstract; // `PlayerConnected` is called the moment we detect a new player on a server. -static function PlayerConnected(ConnectionService.Connection connection); +static function ConnectionEstablished(ConnectionService.Connection connection); // `PlayerDisconnected` is called the moment we // detect a player leaving the server. -static function PlayerDisconnected(ConnectionService.Connection connection); +static function ConnectionLost(ConnectionService.Connection connection); defaultproperties { diff --git a/sources/Services/Connection/ConnectionService.uc b/sources/Services/Connection/ConnectionService.uc index a374813..974940e 100644 --- a/sources/Services/Connection/ConnectionService.uc +++ b/sources/Services/Connection/ConnectionService.uc @@ -25,7 +25,7 @@ class ConnectionService extends Service; struct Connection { var public string networkAddress; - var public string steamID; + var public string idHash; var public PlayerController controllerReference; // Reference to `AcediaReplicationInfo` for this client, // in case it was created. @@ -38,11 +38,12 @@ var private array activeConnections; // `class'ConnectionEvents'` every time. var const class events; -// Find all players manually on launch +// Clean disconnected and manually find all new players on launch protected function OnLaunch() { local Controller nextController; local PlayerController nextPlayerController; + RemoveBrokenConnections(); nextController = level.controllerList; while (nextController != none) { @@ -98,7 +99,7 @@ private function RemoveBrokenConnections() if (activeConnections[i].acediaRI != none) { activeConnections[i].acediaRI.Destroy(); } - events.static.CallPlayerDisconnected(activeConnections[i]); + events.static.CallConnectionLost(activeConnections[i]); activeConnections.Remove(i, 1); } else { @@ -147,13 +148,13 @@ public final function bool RegisterConnection(PlayerController player) newConnection.acediaRI = Spawn(class'AcediaReplicationInfo', player); newConnection.acediaRI.linkOwner = player; }*/ + newConnection.idHash = player.GetPlayerIDHash(); newConnection.networkAddress = player.GetPlayerNetworkAddress(); - newConnection.steamID = player.GetPlayerIDHash(); activeConnections[activeConnections.length] = newConnection; // Remember recorded connections in case someone decides to // nuke this service default.activeConnections = activeConnections; - events.static.CallPlayerConnected(newConnection); + events.static.CallConnectionEstablished(newConnection); return true; } diff --git a/sources/Players/Tests/TEST_Player.uc b/sources/Users/Tests/TEST_User.uc similarity index 83% rename from sources/Players/Tests/TEST_Player.uc rename to sources/Users/Tests/TEST_User.uc index f85beea..6120ecf 100644 --- a/sources/Players/Tests/TEST_Player.uc +++ b/sources/Users/Tests/TEST_User.uc @@ -17,7 +17,8 @@ * You should have received a copy of the GNU General Public License * along with Acedia. If not, see . */ -class TEST_Player extends TestCase +class TEST_User extends TestCase + dependson(UserID) abstract; protected static function TESTS() @@ -40,19 +41,19 @@ protected static function Test_UserID() Issue("`UserID` incorrectly handles SteamID."); TEST_ExpectTrue(testID.GetUniqueID() == "76561198025127722"); - TEST_ExpectTrue(testID.GetSteamID() == "STEAM_1:0:32430997"); - TEST_ExpectTrue(testID.GetSteamID3() == "U:1:64861994"); - TEST_ExpectTrue(testID.GetSteamID32() == 64861994); - TEST_ExpectTrue(testID.GetSteamID64() == "76561198025127722"); + TEST_ExpectTrue(testID.GetSteamIDString() == "STEAM_1:0:32430997"); + TEST_ExpectTrue(testID.GetSteamID3String() == "U:1:64861994"); + TEST_ExpectTrue(testID.GetSteamID32String() == 64861994); + TEST_ExpectTrue(testID.GetSteamID64String() == "76561198025127722"); Issue("Two `UserID` equality check is incorrect."); testID2 = UserID(_().memory.Allocate(class'UserID')); testID3 = UserID(_().memory.Allocate(class'UserID')); testID2.Initialize("76561198025127722"); testID3.Initialize("76561198044316328"); - TEST_ExpectTrue(testID.IsEqual(testID2)); + TEST_ExpectTrue(testID.IsEqualTo(testID2)); TEST_ExpectTrue(testID.IsEqualToSteamID(testID2.GetSteamID())); - TEST_ExpectFalse(testID3.IsEqual(testID)); + TEST_ExpectFalse(testID3.IsEqualTo(testID)); Issue("Steam data returned by `UserID` is incorrect."); SteamID = testID3.GetSteamID(); @@ -65,5 +66,5 @@ protected static function Test_UserID() defaultproperties { - caseName = "Player" + caseName = "User" } \ No newline at end of file diff --git a/sources/Users/User.uc b/sources/Users/User.uc new file mode 100644 index 0000000..7486c09 --- /dev/null +++ b/sources/Users/User.uc @@ -0,0 +1,47 @@ +/** + * Objects of this class are meant to represent a "server user": + * not a particular `PlayerController`, but an entity that server would + * recognize to be the same person even after reconnections. + * It is supposed to store and recognize various stats and + * server privileges. + * Copyright 2020 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 User extends AcediaObject; + +var private UserID id; +var private int key; + +public final function Initialize(UserID initID, int initKey) +{ + id = initID; + key = initKey; +} + +public final function UserID GetID() +{ + return id; +} + +public final function int GetKey() +{ + return key; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Users/UserAPI.uc b/sources/Users/UserAPI.uc new file mode 100644 index 0000000..189b060 --- /dev/null +++ b/sources/Users/UserAPI.uc @@ -0,0 +1,53 @@ +/** + * API that provides functions for managing objects and actors by providing + * easy and general means to create and destroy them, that allow to make use of + * temporary `Object`s in a more efficient way. + * This is a low-level API that most users of Acedia, most likely, + * would not have to use, since creation of most objects would use their own + * wrapper functions around this API. + * Copyright 2020 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 UserAPI extends Singleton; + +public final function UserDatabase GetDatabase() +{ + return class'UserDatabase'.static.GetInstance(); +} + +public final function User Fetch(UserID userID) +{ + return class'UserDatabase'.static.GetInstance().FetchUser(userID); +} + +public final function User FetchByIDHash(string idHash) +{ + local UserID userID; + local UserDatabase userDB; + userDB = class'UserDatabase'.static.GetInstance(); + userID = userDB.FetchUserID(idHash); + return userDB.FetchUser(userID); +} + +public final function User FetchByKey(int userKey) +{ + return class'UserDatabase'.static.GetInstance().FetchUserByKey(userKey); +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Users/UserDatabase.uc b/sources/Users/UserDatabase.uc new file mode 100644 index 0000000..1fc3469 --- /dev/null +++ b/sources/Users/UserDatabase.uc @@ -0,0 +1,86 @@ +/** + * Simple user database for Acedia. + * Copyright 2020 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 UserDatabase extends AcediaObject + config(Acedia) + abstract; + +var private UserDatabase activeDatabase; +var private array sessionUsers; +var private array storedUserIDs; + +public final static function UserDatabase GetInstance() +{ + if (default.activeDatabase == none) + { + default.activeDatabase = + UserDatabase(_().memory.Allocate(class'UserDatabase')); + } + return default.activeDatabase; +} + +public final function UserID FetchUserID(string idHash) +{ + local int i; + local UserID.SteamID steamID; + local UserID newUserID; + steamID = class'UserID'.static.GetSteamIDFromIDHash(idHash); + for (i = 0; i < storedUserIDs.length; i += 1) + { + if (storedUserIDs[i].IsEqualToSteamID(steamID)) { + return storedUserIDs[i]; + } + } + newUserID = UserID(_().memory.Allocate(class'UserID')); + newUserID.InitializeWithSteamID(steamID); + storedUserIDs[storedUserIDs.length] = newUserID; + return newUserID; +} + +public final function User FetchUser(UserID userID) +{ + local int i; + local User newUser; + for (i = 0; i < sessionUsers.length; i += 1) + { + if (sessionUsers[i].GetID().IsEqualTo(userID)) { + return sessionUsers[i]; + } + } + newUser = User(_().memory.Allocate(class'User')); + newUser.Initialize(userID, sessionUsers.length + 1); + sessionUsers[sessionUsers.length] = newUser; + return newUser; +} + +public final function User FetchUserByKey(int userKey) +{ + local int i; + for (i = 0; i < sessionUsers.length; i += 1) + { + if (sessionUsers[i].GetKey() == userKey) { + return sessionUsers[i]; + } + } + return none; +} + +defaultproperties +{ +} \ No newline at end of file diff --git a/sources/Players/UserID.uc b/sources/Users/UserID.uc similarity index 91% rename from sources/Players/UserID.uc rename to sources/Users/UserID.uc index 8336a21..94d47ea 100644 --- a/sources/Players/UserID.uc +++ b/sources/Users/UserID.uc @@ -133,7 +133,7 @@ private final function string GetSteamAccountTypeCharacter() * @param steamID64 Steam64 ID's decimal representation in a plain string. * @return `SteamID` generated from a given Steam64 ID `steamID64`. */ -public static final function SteamID GetSteamIDFromSteamID64( +public static final function SteamID GetSteamIDFromIDHash( string steamID64) { local int i; @@ -178,8 +178,29 @@ public final function bool Initialize(string steamID64) if (initialized) { return false; } + initializedData = GetSteamIDFromIDHash(steamID64); + initialized = true; + return true; +} - initializedData = GetSteamIDFromSteamID64(steamID64); +/** + * Initializes caller `UserID` from a given `SteamID` structure. + * + * Each `UserID` can only be initialized once and becomes immutable + * afterwards. + * + * @param steamID Valid `SteamID` structure that caller `UserID` will + * represent. + * + * @return `true` if initialization was successful and `false` otherwise + * (can only happen if caller `UserID` was already initialized). + */ +public final function bool InitializeWithSteamID(SteamID steamID) +{ + if (initialized) { + return false; + } + initializedData = steamID; initialized = true; return true; } @@ -217,7 +238,7 @@ public final function SteamID GetSteamID() * `false` otherwise. If at least one of the `UserID`s being compared is * uninitialized, the result will be `false`. */ -public final function bool IsEqual(UserID otherID) +public final function bool IsEqualTo(UserID otherID) { if (!IsInitialized()) return false; if (!otherID.IsInitialized()) return false; @@ -268,7 +289,7 @@ public final function string GetUniqueID() * @return String representation of the caller `UserID` in * form "STEAM_X:Y:Z" if it was initialized and empty `string` otherwise. */ -public final function string GetSteamID() +public final function string GetSteamIDString() { local int Y, Z; Y = 0; @@ -294,7 +315,7 @@ public final function string GetSteamID() * @return String representation of the caller `UserID` in * form "C:U:A" if it was initialized and empty `string` otherwise. */ -public final function string GetSteamID3() +public final function string GetSteamID3String() { return (GetSteamAccountTypeCharacter() $ ":" $ initializedData.universe @@ -310,7 +331,7 @@ public final function string GetSteamID3() * @return Unique `int` representation of the caller `UserID` * if it was initialized and `-1` otherwise. */ -public final function int GetSteamID32() +public final function int GetSteamID32String() { if (!IsInitialized()) return -1; return initializedData.steamID32; @@ -329,7 +350,7 @@ public final function int GetSteamID32() * @return String representation of the Steam64 ID of the caller `UserID` * if it was initialized and empty `string` otherwise. */ -public final function string GetSteamID64() +public final function string GetSteamID64String() { if (!IsInitialized()) return ""; return initializedData.steamID64;