Browse Source

Refactor `APlayer`/`User` system

Refactors `User` to simply store persistent data, while making `APlayer`
represent a connecte playr with appropriate links to `User` and (in
future) his in-game pawn representation.
pull/8/head
Anton Tarasenko 4 years ago
parent
commit
ba64e362c7
  1. 3
      sources/Global.uc
  2. 2
      sources/Manifest.uc
  3. 43
      sources/Players/APlayer.uc
  4. 52
      sources/Players/ConnectionListener_Player.uc
  5. 51
      sources/Players/PlayerEvents.uc
  6. 33
      sources/Players/PlayerListenerBase.uc
  7. 86
      sources/Players/PlayerService.uc
  8. 9
      sources/Services/Connection/ConnectionEvents.uc
  9. 4
      sources/Services/Connection/ConnectionListenerBase.uc
  10. 11
      sources/Services/Connection/ConnectionService.uc
  11. 17
      sources/Users/Tests/TEST_User.uc
  12. 47
      sources/Users/User.uc
  13. 53
      sources/Users/UserAPI.uc
  14. 86
      sources/Users/UserDatabase.uc
  15. 35
      sources/Users/UserID.uc

3
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());
}

2
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'
}

43
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 <https://www.gnu.org/licenses/>.
*/
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<PlayerEvents> 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'
}

52
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 <https://www.gnu.org/licenses/>.
*/
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'
}

51
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 <https://www.gnu.org/licenses/>.
*/
class PlayerEvents extends Events
abstract;
static function CallPlayerConnected(APlayer newPlayer)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<PlayerListenerBase>(listeners[i])
.static.PlayerConnected(newPlayer);
}
}
static function CallPlayerDisconnected(APlayer lostPlayer)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<PlayerListenerBase>(listeners[i])
.static.PlayerDisconnected(lostPlayer);
}
}
defaultproperties
{
relatedListener = class'PlayerListenerBase'
connectedServiceClass = class'PlayerService'
}

33
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 <https://www.gnu.org/licenses/>.
*/
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'
}

86
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 <https://www.gnu.org/licenses/>.
*/
class PlayerService extends Service;
var private array<APlayer> 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<APlayer> 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
{
}

9
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<Listener> > listeners;
@ -29,11 +30,11 @@ static function CallPlayerConnected(ConnectionService.Connection connection)
for (i = 0; i < listeners.length; i += 1)
{
class<ConnectionListenerBase>(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<Listener> > listeners;
@ -41,7 +42,7 @@ static function CallPlayerDisconnected(ConnectionService.Connection connection)
for (i = 0; i < listeners.length; i += 1)
{
class<ConnectionListenerBase>(listeners[i])
.static.PlayerDisconnected(connection);
.static.ConnectionLost(connection);
}
}

4
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
{

11
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<Connection> activeConnections;
// `class'ConnectionEvents'` every time.
var const class<ConnectionEvents> 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;
}

17
sources/Players/Tests/TEST_Player.uc → 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 <https://www.gnu.org/licenses/>.
*/
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"
}

47
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 <https://www.gnu.org/licenses/>.
*/
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
{
}

53
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 <https://www.gnu.org/licenses/>.
*/
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
{
}

86
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 <https://www.gnu.org/licenses/>.
*/
class UserDatabase extends AcediaObject
config(Acedia)
abstract;
var private UserDatabase activeDatabase;
var private array<User> sessionUsers;
var private array<UserID> 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
{
}

35
sources/Players/UserID.uc → 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;
Loading…
Cancel
Save