diff --git a/sources/Global.uc b/sources/Global.uc
index 3f9a88b..0e046d4 100644
--- a/sources/Global.uc
+++ b/sources/Global.uc
@@ -37,6 +37,7 @@ var public MemoryAPI memory;
var public ConsoleAPI console;
var public ColorAPI color;
var public UserAPI users;
+var public PlayersAPI players;
var public JSONAPI json;
public final static function Global GetInstance()
@@ -67,6 +68,7 @@ protected function Initialize()
console = ConsoleAPI(memory.Allocate(class'ConsoleAPI'));
color = ColorAPI(memory.Allocate(class'ColorAPI'));
users = UserAPI(memory.Allocate(class'UserAPI'));
+ players = PlayersAPI(memory.Allocate(class'PlayersAPI'));
json = JSONAPI(memory.Allocate(class'JSONAPI'));
json.StaticConstructor();
}
\ No newline at end of file
diff --git a/sources/Players/APlayer.uc b/sources/Players/APlayer.uc
index 0d9e199..8d6ca32 100644
--- a/sources/Players/APlayer.uc
+++ b/sources/Players/APlayer.uc
@@ -33,6 +33,8 @@ var private ConsoleWriter consoleInstance;
// Remember version to reallocate writer in case someone deallocates it
var private int consoleLifeVersion;
+var private NativeActorRef controller;
+
// These variables record name of this player;
// `hashedName` is used to track outside changes that bypass our getter/setter.
var private Text textName;
@@ -49,19 +51,9 @@ enum AdminStatus
AS_SilentAdmin
};
-// `PlayerController` associated with the caller `APLayer`.
-// Can return `none` if:
-// 1. Caller `APlayer` has already disconnected;
-// 2. It was not properly initialized;
-// 3. There is an issue running `PlayerService`.
-private final function PlayerController GetController()
+protected function Finalizer()
{
- local PlayerService service;
- service = PlayerService(class'PlayerService'.static.Require());
- if (service != none) {
- return service.GetController(self);
- }
- return none;
+ _.memory.Free(controller);
}
// `PlayerReplicationInfo` associated with the caller `APLayer`.
@@ -72,7 +64,7 @@ private final function PlayerController GetController()
private final function PlayerReplicationInfo GetRI()
{
local PlayerController myController;
- myController = GetController();
+ myController = PlayerController(controller.Get());
if (myController != none) {
return myController.playerReplicationInfo;
}
@@ -88,7 +80,7 @@ private final function PlayerReplicationInfo GetRI()
*/
public final function bool IsConnected()
{
- return (GetController() != none);
+ return (controller.Get() != none);
}
/**
@@ -110,6 +102,7 @@ public final function Initialize(Text idHash)
// Retrieve controller and replication info
service = PlayerService(class'PlayerService'.static.Require());
myController = service.GetController(self);
+ controller = _.unreal.ActorRef(myController);
if (myController != none) {
myReplicationInfo = myController.playerReplicationInfo;
}
diff --git a/sources/Players/ConnectionListener_Player.uc b/sources/Players/ConnectionListener_Player.uc
deleted file mode 100644
index 607c86a..0000000
--- a/sources/Players/ConnectionListener_Player.uc
+++ /dev/null
@@ -1,52 +0,0 @@
-/**
- * `PlayerService`'s 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;
-
-var LoggerAPI.Definition fatalNoPlayerService;
-
-static function ConnectionEstablished(ConnectionService.Connection connection)
-{
- local PlayerService service;
- service = PlayerService(class'PlayerService'.static.Require());
- if (service == none)
- {
- __().logger.Auto(default.fatalNoPlayerService);
- return;
- }
- service.RegisterPlayer(connection.controllerReference);
-}
-
-static function ConnectionLost(ConnectionService.Connection connection)
-{
- local PlayerService service;
- service = PlayerService(class'PlayerService'.static.Require());
- if (service == none)
- {
- __().logger.Auto(default.fatalNoPlayerService);
- return;
- }
- service.UpdateAllPlayers();
-}
-
-defaultproperties
-{
- relatedEvents = class'ConnectionEvents'
- fatalNoPlayerService = (l=LOG_Fatal,m="Cannot start `PlayerService` service Acedia will not properly work from now on.")
-}
\ No newline at end of file
diff --git a/sources/Players/PlayerEvents.uc b/sources/Players/PlayerEvents.uc
deleted file mode 100644
index 14f2be7..0000000
--- a/sources/Players/PlayerEvents.uc
+++ /dev/null
@@ -1,51 +0,0 @@
-/**
- * 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/PlayerService.uc b/sources/Players/PlayerService.uc
index 869a796..7bd9049 100644
--- a/sources/Players/PlayerService.uc
+++ b/sources/Players/PlayerService.uc
@@ -22,61 +22,65 @@
*/
class PlayerService extends Service;
-// Used to 1-to-1 associate `APlayer` with `PlayerController` object.
+// Used to 1-to-1 associate `APlayer` objects with `PlayerController` actors.
struct PlayerControllerPair
{
var APlayer player;
var PlayerController controller;
};
-// Store all known players along with their `PlayerController`s.
+// Records of all known pairs
var private array allPlayers;
-// Shortcut to `ConnectionEvents`, so that we don't have to write
-// `class'ConnectionEvents'` every time.
-var const class events;
+protected function Contructor()
+{
+ SetTimer(1.0, true);
+}
+
+protected function Finalizer()
+{
+ SetTimer(0.0, false);
+}
/**
* Creates a new `APlayer` instance for a given `newPlayerController`
* controller.
*
- * If given controller is `none` or it's `APLayer` was already created,
+ * If given controller is `none` or it's `APlayer` was already created,
* - does nothing.
*
* @param newPlayerController Controller for which we must
* create new `APlayer`.
* @return `true` if new `APlayer` was created and `false` otherwise.
*/
-public final function bool RegisterPlayer(PlayerController newPlayerController)
+public final function bool RegisterPair(
+ PlayerController newController,
+ APlayer newPlayer)
{
local int i;
- local Text textIdHash;
local PlayerControllerPair newPair;
- if (newPlayerController == none) return false;
+ if (newController == none) return false;
+ if (newPlayer == none) return false;
- UpdateAllPlayers();
for (i = 0; i < allPlayers.length; i += 1)
{
- if (allPlayers[i].controller == newPlayerController) {
+ if (allPlayers[i].controller == newController) {
+ return false;
+ }
+ if (allPlayers[i].player == newPlayer) {
return false;
}
}
// Record new pair in service's data
- newPair.controller = newPlayerController;
- newPair.player = APlayer(_.memory.Allocate(class'APlayer', true));
+ newPair.controller = newController;
+ newPair.player = newPlayer;
allPlayers[allPlayers.length] = newPair;
- // Initialize new `APlayer`
- textIdHash = _.text.FromString(newPlayerController.GetPlayerIDHash());
- newPair.player.Initialize(textIdHash);
- textIdHash.FreeSelf();
- // Run events
- events.static.CallPlayerConnected(newPair.player);
return true;
}
/**
- * Fetches current array of all player (registered `APLayer`s).
+ * Fetches current array of all players (registered `APlayer`s).
*
- * @return Current array of all player (registered `APLayer`s). Guaranteed to
+ * @return Current array of all players (registered `APlayer`s). Guaranteed to
* not contain `none` values.
*/
public final function array GetAllPlayers()
@@ -99,7 +103,7 @@ public final function array GetAllPlayers()
* @return `APlayer` that is associated with a given `PlayerController`.
* Can return `none` if player has already "expired".
*/
-public final function APlayer GetPlayer(PlayerController controller)
+public final function APlayer GetPlayer(Controller controller)
{
local int i;
if (controller == none) {
@@ -143,14 +147,12 @@ public final function PlayerController GetController(APlayer player)
* Causes status of all players to update.
* See `APlayer.Update()` for details.
*/
-public final function UpdateAllPlayers()
+event Timer()
{
local int i;
while (i < allPlayers.length)
{
- if (allPlayers[i].controller == none)
- {
- events.static.CallPlayerDisconnected(allPlayers[i].player);
+ if (allPlayers[i].controller == none || allPlayers[i].player == none) {
allPlayers.Remove(i, 1);
}
else {
@@ -161,6 +163,4 @@ public final function UpdateAllPlayers()
defaultproperties
{
- events = class'PlayerEvents'
- requiredListeners(0) = class'ConnectionListener_Player'
}
\ No newline at end of file
diff --git a/sources/Players/PlayersAPI.uc b/sources/Players/PlayersAPI.uc
new file mode 100644
index 0000000..d658044
--- /dev/null
+++ b/sources/Players/PlayersAPI.uc
@@ -0,0 +1,99 @@
+/**
+ * API that provides functions for working player references (`APlayer`).
+ * Copyright 2021 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 PlayersAPI extends AcediaObject
+ dependson(Text);
+
+// Writer that can be used to write into this player's console
+var private ConsoleWriter consoleInstance;
+// Remember version to reallocate writer in case someone deallocates it
+var private int consoleLifeVersion;
+
+protected function Constructor()
+{
+ local ConnectionService service;
+ service = ConnectionService(class'ConnectionService'.static.Require());
+ service.OnConnectionEstablished(self).connect = MakePlayer;
+}
+
+protected function Finalizer()
+{
+ local ConnectionService service;
+ service = ConnectionService(class'ConnectionService'.static.Require());
+ service.OnConnectionEstablished(self).Disconnect();
+}
+
+private final function MakePlayer(ConnectionService.Connection newConnection)
+{
+ local APlayer newPlayer;
+ local Text textIdHash;
+ local PlayerService service;
+ // Make new player controller and link it to `newConnection`
+ newPlayer = APlayer(_.memory.Allocate(class'APlayer'));
+ service = PlayerService(class'PlayerService'.static.Require());
+ service.RegisterPair(newConnection.controllerReference, newPlayer);
+ // Initialize new `APlayer`
+ textIdHash = _.text.FromString(newConnection.idHash);
+ newPlayer.Initialize(textIdHash);
+ textIdHash.FreeSelf();
+}
+
+/**
+ * Return `ConsoleWriter` that can be used to write into every player's
+ * console.
+ *
+ * Provided that returned object is never deallocated - returns the same object
+ * with each call, otherwise can allocate new instance of `ConsoleWriter`.
+ *
+ * @return `ConsoleWriter` that can be used to write into every player's
+ * console. Returned object should not be deallocated, but it is
+ * guaranteed to be valid for non-disconnected players.
+ */
+public final function ConsoleWriter Console()
+{
+ if ( consoleInstance == none
+ || consoleInstance.GetLifeVersion() != consoleLifeVersion)
+ {
+ consoleInstance = _.console.ForAll();
+ consoleLifeVersion = consoleInstance.GetLifeVersion();
+ }
+ // Set everybody as a target in case someone messed with this setting
+ return consoleInstance.ForAll();
+}
+
+/**
+ * Fetches current array of all players.
+ *
+ * @return Current array of all players.
+ * Guaranteed to not contain `none` values.
+ */
+public final function array GetPlayers()
+{
+ local PlayerService service;
+ local array emptyResult;
+ service = PlayerService(class'PlayerService'.static.GetInstance());
+ if (service != none) {
+ return service.GetAllPlayers();
+ }
+ return emptyResult;
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Services/Connection/ConnectionEvents.uc b/sources/Services/Connection/ConnectionEvents.uc
deleted file mode 100644
index 5adcb0d..0000000
--- a/sources/Services/Connection/ConnectionEvents.uc
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * 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 ConnectionEvents extends Events
- dependson(ConnectionService)
- abstract;
-
-static function CallConnectionEstablished(
- ConnectionService.Connection connection)
-{
- local int i;
- local array< class > listeners;
- listeners = GetListeners();
- for (i = 0; i < listeners.length; i += 1)
- {
- class(listeners[i])
- .static.ConnectionEstablished(connection);
- }
-}
-
-static function CallConnectionLost(ConnectionService.Connection connection)
-{
- local int i;
- local array< class > listeners;
- listeners = GetListeners();
- for (i = 0; i < listeners.length; i += 1)
- {
- class(listeners[i])
- .static.ConnectionLost(connection);
- }
-}
-
-defaultproperties
-{
- relatedListener = class'ConnectionListenerBase'
- connectedServiceClass = class'ConnectionService'
-}
\ No newline at end of file
diff --git a/sources/Services/Connection/ConnectionService.uc b/sources/Services/Connection/ConnectionService.uc
index c1a4088..82f8e1f 100644
--- a/sources/Services/Connection/ConnectionService.uc
+++ b/sources/Services/Connection/ConnectionService.uc
@@ -34,15 +34,47 @@ struct Connection
var private array activeConnections;
-// Shortcut to `ConnectionEvents`, so that we don't have to write
-// `class'ConnectionEvents'` every time.
-var const class events;
+var private Connection_Signal onConnectionEstablishedSignal;
+var private Connection_Signal onConnectionLostSignal;
+
+/**
+ * Signal that will be emitted when new player connection is established.
+ *
+ * [Signature]
+ * void (ConnectionService.Connection newConnection)
+ *
+ * @param newConnection Structure that describes new connection.
+ */
+/* SIGNAL */
+public final function Connection_Slot OnConnectionEstablished(
+ AcediaObject receiver)
+{
+ return Connection_Slot(onConnectionEstablishedSignal.NewSlot(receiver));
+}
+
+/**
+ * Signal that will be emitted when the player connection is lost.
+ *
+ * [Signature]
+ * void (ConnectionService.Connection newConnection)
+ *
+ * @param newConnection Structure that describes lost connection.
+ */
+/* SIGNAL */
+public final function Connection_Slot OnConnectionLost(AcediaObject receiver)
+{
+ return Connection_Slot(onConnectionLostSignal.NewSlot(receiver));
+}
// Clean disconnected and manually find all new players on launch
protected function OnLaunch()
{
local Controller nextController;
local PlayerController nextPlayerController;
+ onConnectionEstablishedSignal =
+ Connection_Signal(_.memory.Allocate(class'Connection_Signal'));
+ onConnectionLostSignal =
+ Connection_Signal(_.memory.Allocate(class'Connection_Signal'));
RemoveBrokenConnections();
nextController = level.controllerList;
while (nextController != none)
@@ -55,6 +87,13 @@ protected function OnLaunch()
}
}
+protected function OnShutdown()
+{
+ default.activeConnections = activeConnections;
+ _.memory.Free(onConnectionEstablishedSignal);
+ _.memory.Free(onConnectionLostSignal);
+}
+
// Returning `true` guarantees that `controllerToCheck != none`
// and `controllerToCheck.playerReplicationInfo != none`.
private function bool IsHumanController(PlayerController controllerToCheck)
@@ -99,7 +138,7 @@ private function RemoveBrokenConnections()
if (activeConnections[i].acediaRI != none) {
activeConnections[i].acediaRI.Destroy();
}
- events.static.CallConnectionLost(activeConnections[i]);
+ onConnectionLostSignal.Emit(activeConnections[i]);
activeConnections.Remove(i, 1);
}
else {
@@ -143,19 +182,14 @@ public final function bool RegisterConnection(PlayerController player)
if (!IsHumanController(player)) return false;
if (GetConnectionIndex(player) >= 0) return true;
newConnection.controllerReference = player;
- // TODO: move this check to AcediaCore
- /*if (!class'Acedia'.static.GetInstance().IsServerOnly())
- {
- newConnection.acediaRI = Spawn(class'AcediaReplicationInfo', player);
- newConnection.acediaRI.linkOwner = player;
- }*/
+
newConnection.idHash = player.GetPlayerIDHash();
newConnection.networkAddress = player.GetPlayerNetworkAddress();
activeConnections[activeConnections.length] = newConnection;
// Remember recorded connections in case someone decides to
// nuke this service
default.activeConnections = activeConnections;
- events.static.CallConnectionEstablished(newConnection);
+ onConnectionEstablishedSignal.Emit(newConnection);
return true;
}
@@ -192,6 +226,5 @@ event Tick(float delta)
defaultproperties
{
- events = class'ConnectionEvents'
requiredListeners(0) = class'MutatorListener_Connection'
}
\ No newline at end of file
diff --git a/sources/Services/Connection/ConnectionListenerBase.uc b/sources/Services/Connection/Events/Connection_Signal.uc
similarity index 50%
rename from sources/Services/Connection/ConnectionListenerBase.uc
rename to sources/Services/Connection/Events/Connection_Signal.uc
index f82fe0c..b8de7ce 100644
--- a/sources/Services/Connection/ConnectionListenerBase.uc
+++ b/sources/Services/Connection/Events/Connection_Signal.uc
@@ -1,6 +1,6 @@
/**
- * Listener for events generated by 'ConnectionService'.
- * Copyright 2019 Anton Tarasenko
+ * Signal class implementation for `ConnectionServices`.
+ * Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@@ -17,27 +17,22 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class ConnectionListenerBase extends Listener
- dependson(ConnectionService)
- abstract;
+class Connection_Signal extends Signal;
-/**
- * Called the moment we detect new "established" connection:
- * connection for player with already created `PlayerController` and defined
- * steam data/id.
- *
- * @param connection Structure, describing new connection.
- */
-static function ConnectionEstablished(ConnectionService.Connection connection);
-
-/**
- * Called the moment we detect a lost connection.
- *
- * @param connection Structure, describing now dead connection.
- */
-static function ConnectionLost(ConnectionService.Connection connection);
+public final function Emit(ConnectionService.Connection connection)
+{
+ local Slot nextSlot;
+ StartIterating();
+ nextSlot = GetNextSlot();
+ while (nextSlot != none)
+ {
+ Connection_Slot(nextSlot).connect(connection);
+ nextSlot = GetNextSlot();
+ }
+ CleanEmptySlots();
+}
defaultproperties
{
- relatedEvents = class'ConnectionEvents'
+ relatedSlotClass = class'Connection_Slot'
}
\ No newline at end of file
diff --git a/sources/Players/PlayerListenerBase.uc b/sources/Services/Connection/Events/Connection_Slot.uc
similarity index 56%
rename from sources/Players/PlayerListenerBase.uc
rename to sources/Services/Connection/Events/Connection_Slot.uc
index 49c5630..eb7ae7c 100644
--- a/sources/Players/PlayerListenerBase.uc
+++ b/sources/Services/Connection/Events/Connection_Slot.uc
@@ -1,6 +1,6 @@
/**
- * Listener for events generated by 'ConnectionService'.
- * Copyright 2019 Anton Tarasenko
+ * Slot class implementation for `ConnectionServices`.
+ * Copyright 2021 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@@ -17,25 +17,25 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class PlayerListenerBase extends Listener
- abstract;
+class Connection_Slot extends Slot
+ dependson(ConnectionService);
-/**
- * `PlayerConnected` is called the moment we detect a new player on a server.
- *
- * @param newPlayer Player that just connected.
- */
-static function PlayerConnected(APlayer newPlayer);
+delegate connect(ConnectionService.Connection connection)
+{
+ DummyCall();
+}
-/**
- * `PlayerDisconnected` is called the moment we detect a player leaving
- * the server.
- *
- * @param lostPlayer Player that just disconnected.
- */
-static function PlayerDisconnected(APlayer lostPlayer);
+protected function Constructor()
+{
+ connect = none;
+}
+
+protected function Finalizer()
+{
+ super.Finalizer();
+ connect = none;
+}
defaultproperties
{
- relatedEvents = class'PlayerEvents'
}
\ No newline at end of file