Browse Source

Change how new players are tracked

pull/8/head
Anton Tarasenko 4 years ago
parent
commit
89d9216b59
  1. 2
      sources/Global.uc
  2. 21
      sources/Players/APlayer.uc
  3. 52
      sources/Players/ConnectionListener_Player.uc
  4. 51
      sources/Players/PlayerEvents.uc
  5. 56
      sources/Players/PlayerService.uc
  6. 99
      sources/Players/PlayersAPI.uc
  7. 53
      sources/Services/Connection/ConnectionEvents.uc
  8. 57
      sources/Services/Connection/ConnectionService.uc
  9. 37
      sources/Services/Connection/Events/Connection_Signal.uc
  10. 36
      sources/Services/Connection/Events/Connection_Slot.uc

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

21
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;
}

52
sources/Players/ConnectionListener_Player.uc

@ -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 <https://www.gnu.org/licenses/>.
*/
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.")
}

51
sources/Players/PlayerEvents.uc

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

56
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<PlayerControllerPair> allPlayers;
// Shortcut to `ConnectionEvents`, so that we don't have to write
// `class'ConnectionEvents'` every time.
var const class<PlayerEvents> 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<APlayer> GetAllPlayers()
@ -99,7 +103,7 @@ public final function array<APlayer> 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'
}

99
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 <https://www.gnu.org/licenses/>.
*/
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<APlayer> GetPlayers()
{
local PlayerService service;
local array<APlayer> emptyResult;
service = PlayerService(class'PlayerService'.static.GetInstance());
if (service != none) {
return service.GetAllPlayers();
}
return emptyResult;
}
defaultproperties
{
}

53
sources/Services/Connection/ConnectionEvents.uc

@ -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 <https://www.gnu.org/licenses/>.
*/
class ConnectionEvents extends Events
dependson(ConnectionService)
abstract;
static function CallConnectionEstablished(
ConnectionService.Connection connection)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<ConnectionListenerBase>(listeners[i])
.static.ConnectionEstablished(connection);
}
}
static function CallConnectionLost(ConnectionService.Connection connection)
{
local int i;
local array< class<Listener> > listeners;
listeners = GetListeners();
for (i = 0; i < listeners.length; i += 1)
{
class<ConnectionListenerBase>(listeners[i])
.static.ConnectionLost(connection);
}
}
defaultproperties
{
relatedListener = class'ConnectionListenerBase'
connectedServiceClass = class'ConnectionService'
}

57
sources/Services/Connection/ConnectionService.uc

@ -34,15 +34,47 @@ struct Connection
var private array<Connection> activeConnections;
// Shortcut to `ConnectionEvents`, so that we don't have to write
// `class'ConnectionEvents'` every time.
var const class<ConnectionEvents> events;
var private Connection_Signal onConnectionEstablishedSignal;
var private Connection_Signal onConnectionLostSignal;
/**
* Signal that will be emitted when new player connection is established.
*
* [Signature]
* void <slot>(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 <slot>(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'
}

37
sources/Services/Connection/ConnectionListenerBase.uc → 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 <https://www.gnu.org/licenses/>.
*/
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'
}

36
sources/Players/PlayerListenerBase.uc → 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 <https://www.gnu.org/licenses/>.
*/
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'
}
Loading…
Cancel
Save