You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
470 lines
15 KiB
470 lines
15 KiB
/** |
|
* Provides a common interface to a connected player connection. |
|
* Copyright 2021 - 2022 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 EPlayer extends EInterface; |
|
|
|
// How this `EPlayer` is identified by the server |
|
var private User identity; |
|
|
|
// 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; |
|
|
|
// `PlayerController` reference |
|
var private NativeActorRef controller; |
|
|
|
/** |
|
* Describes the player's admin status (as defined by standard KF classes) |
|
*/ |
|
enum AdminStatus |
|
{ |
|
// Not an admin |
|
AS_None, |
|
// (Publicly visible) admin |
|
AS_Admin, |
|
// Admin with their admin status hidden |
|
AS_SilentAdmin |
|
}; |
|
|
|
// Stores all the types of signals `EPlayer` might emit |
|
struct PlayerSignals |
|
{ |
|
var public PlayerAPI_OnPlayerNameChanging_Signal onNameChanging; |
|
var public PlayerAPI_OnPlayerNameChanged_Signal onNameChanged; |
|
}; |
|
// We do not own objects in this structure, but it is created and managed by |
|
// `PlayersAPI` and is expected to be allocated during the whole Acedia run. |
|
var protected PlayerSignals signalsReferences; |
|
|
|
protected function Finalizer() |
|
{ |
|
_.memory.Free(controller); |
|
_.memory.Free(consoleInstance); |
|
controller = none; |
|
consoleInstance = none; |
|
// No need to deallocate `User` objects, since they are all have unique |
|
// instance for every player on the server |
|
identity = none; |
|
} |
|
|
|
/** |
|
* Initializes caller `EPlayer`. Should be called right after `EPlayer` |
|
* was allocated. |
|
* |
|
* Every `EPlayer` must be initialized, using non-initialized `EPlayer` |
|
* instances is invalid. |
|
* |
|
* Initialization can fail if: |
|
* 1. `initController == none`; |
|
* 2. Its id hash (from `GetPlayerIDHash()`) was not properly setup yet |
|
* (not steam id); |
|
* 3. Caller `EPlayer` already was successfully initialized. |
|
* |
|
* @param initController Controller that caller `EPlayer` will correspond to. |
|
* @return `true` if initialization was successful and `false` otherwise. |
|
*/ |
|
public final /* unreal */ function bool Initialize( |
|
PlayerController initController, |
|
PlayerSignals playerSignals) |
|
{ |
|
local Text idHash; |
|
if (controller != none) return false; // Already initialized! |
|
if (initController == none) return false; |
|
|
|
if (identity == none) |
|
{ |
|
// Only fetch `User` object if it was not yet setup, which can happen |
|
// if `EPlayer` is making a copy and wants to avoid |
|
// re-fetching `identity` |
|
idHash = _.text.FromString(initController.GetPlayerIDHash()); |
|
identity = _.users.FetchByIDHash(idHash); |
|
idHash.FreeSelf(); |
|
idHash = none; |
|
} |
|
signalsReferences = playerSignals; |
|
controller = _.unreal.ActorRef(initController); |
|
return true; |
|
} |
|
|
|
public function bool IsExistent() |
|
{ |
|
if (controller == none) { |
|
return false; |
|
} |
|
return (controller.Get() != none); |
|
} |
|
|
|
public function EInterface Copy() |
|
{ |
|
local EPlayer playerCopy; |
|
playerCopy = EPlayer(_.memory.Allocate(class'EPlayer')); |
|
if (controller == none) |
|
{ |
|
// Should not really happen, since then caller `EPlayer` was |
|
// not initialized |
|
return playerCopy; |
|
} |
|
playerCopy.identity = identity; |
|
playerCopy.Initialize( PlayerController(controller.Get()), |
|
signalsReferences); |
|
return playerCopy; |
|
} |
|
|
|
public function bool SameAs(EInterface other) |
|
{ |
|
local EPlayer asPlayer; |
|
local NativeActorRef otherController; |
|
if (other == none) return false; |
|
if (controller == none) return false; |
|
asPlayer = EPlayer(other); |
|
if (asPlayer != none) return false; |
|
otherController = asPlayer.controller; |
|
if (otherController == none) return false; |
|
|
|
return (controller.Get() == otherController.Get()); |
|
} |
|
|
|
/** |
|
* Returns color of the caller `EPlayer`'s current team. |
|
* |
|
* Such color is supposed to be defined even for a single-team modes. |
|
* |
|
* @return `Color` structure with the `EPlayer`'s current color. |
|
*/ |
|
public final function Color GetTeamColor() |
|
{ |
|
return _.color.Red; |
|
} |
|
|
|
/** |
|
* Returns location of the caller `EPlayer`. |
|
* |
|
* If caller `EPlayer` has a pawn, then it's location will be returned, |
|
* otherwise a location from which caller `EPlayer` is currently spectating is |
|
* considered caller's location. |
|
* |
|
* @return Location of the caller `EPlayer` has a pawn. |
|
*/ |
|
public final function Vector GetLocation() |
|
{ |
|
local Pawn myPawn; |
|
local PlayerController myController; |
|
if (controller != none) { |
|
myController = PlayerController(controller.Get()); |
|
} |
|
if (myController != none) |
|
{ |
|
myPawn = myController.pawn; |
|
if (myPawn != none) { |
|
return myPawn.location; |
|
} |
|
return myController.location; |
|
} |
|
return Vect(0.0, 0.0, 0.0); |
|
} |
|
|
|
// `PlayerReplicationInfo` associated with the caller `EPlayer`. |
|
// Can return `none` if: |
|
// 1. Caller `EPlayer` has already disconnected; |
|
// 2. It was not properly initialized; |
|
private final function PlayerReplicationInfo GetRI() |
|
{ |
|
local PlayerController myController; |
|
if (controller == none) return none; |
|
myController = PlayerController(controller.Get()); |
|
if (myController == none) return none; |
|
|
|
return myController.playerReplicationInfo; |
|
} |
|
|
|
/** |
|
* Returns `PlayerController`, associated with the caller `EPlayer`. |
|
* |
|
* @return `PlayerController`, associated with the caller `EPlayer`. |
|
*/ |
|
public final /* unreal */ function PlayerController GetController() |
|
{ |
|
if (controller == none) { |
|
return none; |
|
} |
|
return PlayerController(controller.Get()); |
|
} |
|
|
|
/** |
|
* Returns `User` object that is corresponding to the caller `EPlayer`. |
|
* |
|
* @return `User` corresponding to the caller `EPlayer`. Guarantee to be |
|
* not `none` for correctly initialized `EPlayer` (it remembers `User` |
|
* record even if player has disconnected). |
|
*/ |
|
public final function User GetIdentity() |
|
{ |
|
return identity; |
|
} |
|
|
|
/** |
|
* Returns player's original name - the one he joined the game with. |
|
* |
|
* @return `Text` containing original name of the caller player. |
|
* Guaranteed to not be `none`. |
|
*/ |
|
public final function Text GetOriginalName() |
|
{ |
|
local ConnectionService service; |
|
local ConnectionService.Connection myConnection; |
|
service = ConnectionService(class'ConnectionService'.static.Require()); |
|
myConnection = service.GetConnection(GetController()); |
|
return _.text.FromString(myConnection.originalName); |
|
} |
|
|
|
/** |
|
* Returns current displayed name of the caller player. |
|
* |
|
* @return `Text` containing current name of the caller player. |
|
* Guaranteed to not be `none`. Returned object is not managed by caller |
|
* `EPlayer` and should be manually deallocated. |
|
*/ |
|
public final function Text GetName() |
|
{ |
|
local PlayerReplicationInfo myReplicationInfo; |
|
myReplicationInfo = GetRI(); |
|
if (myReplicationInfo != none) { |
|
return _.text.FromColoredString(myReplicationInfo.playerName); |
|
} |
|
return P("").Copy(); |
|
} |
|
|
|
/** |
|
* Set new displayed name for the caller `EPlayer`. |
|
* |
|
* @param newPlayerName New name of the caller `EPlayer`. This value will |
|
* be copied. Passing `none` will result in an empty name. |
|
*/ |
|
public final function SetName(Text newPlayerName) |
|
{ |
|
local Text oldPlayerName; |
|
local PlayerReplicationInfo replicationInfo; |
|
replicationInfo = GetRI(); |
|
if (replicationInfo == none) { |
|
return; |
|
} |
|
if (ConvertTextNameIntoString(newPlayerName) == replicationInfo.playerName) |
|
{ |
|
return; |
|
} |
|
oldPlayerName = _.text.FromFormattedString(replicationInfo.playerName); |
|
replicationInfo.playerName = CensorPlayerName(oldPlayerName, newPlayerName); |
|
_.memory.Free(oldPlayerName); |
|
} |
|
|
|
// Converts `Text` nickname into a suitable `string` representation. |
|
private final function string ConvertTextNameIntoString(Text playerName) |
|
{ |
|
local string newPlayerNameAsString; |
|
local Text.Formatting endingFormatting; |
|
if (playerName == none) { |
|
return ""; |
|
} |
|
newPlayerNameAsString = playerName.ToColoredString(,, _.color.white); |
|
// To correctly display nicknames we want to drop default color tag |
|
// at the beginning (the one `ToColoredString()` adds if first character |
|
// has no defined color). |
|
// This is a compatibility consideration with vanilla UIs that use |
|
// color codes from `myReplicationInfo.playerName` for displaying nicknames |
|
// and whose expected behavior can get broken by default color tag. |
|
if (!playerName.GetFormatting(0).isColored) { |
|
newPlayerNameAsString = Mid(newPlayerNameAsString, 4); |
|
} |
|
// This is another compatibility consideration with vanilla UIs: unless |
|
// we restore color to neutral white, Killing Floor will paint any chat |
|
// messages we send in the color our nickname ended with. |
|
endingFormatting = playerName.GetFormatting(playerName.GetLength() - 1); |
|
if ( endingFormatting.isColored |
|
&& !_.color.AreEqual(endingFormatting.color, _.color.white, true)) |
|
{ |
|
newPlayerNameAsString $= _.color.GetColorTag(_.color.white); |
|
} |
|
return newPlayerNameAsString; |
|
} |
|
|
|
// Calls appropriate events to let them modify / "censor" player's new name. |
|
private final function string CensorPlayerName( |
|
Text oldPlayerName, |
|
Text newPlayerName) |
|
{ |
|
local string result; |
|
local Text censoredName; |
|
local MutableText mutablePlayerName; |
|
if (newPlayerName == none) { |
|
return ""; |
|
} |
|
mutablePlayerName = newPlayerName.MutableCopy(); |
|
// Let signal handlers alter the name |
|
signalsReferences.onNameChanging |
|
.Emit(self, oldPlayerName, mutablePlayerName); |
|
censoredName = mutablePlayerName.Copy(); |
|
signalsReferences.onNameChanged.Emit(self, oldPlayerName, censoredName); |
|
// Returns "censored" result |
|
result = ConvertTextNameIntoString(censoredName); |
|
_.memory.Free(mutablePlayerName); |
|
_.memory.Free(censoredName); |
|
return result; |
|
} |
|
|
|
// TODO: replace this, it has no place here |
|
// ^ works as a temporary solution before we add pawn wrappers |
|
public final function EInventory GetInventory() |
|
{ |
|
local EKFInventory inventory; |
|
if (controller != none && controller.Get() != none) |
|
{ |
|
inventory = EKFInventory(_.memory.Allocate(class'EKFInventory')); |
|
inventory.Initialize(self); |
|
return inventory; |
|
} |
|
return none; |
|
} |
|
|
|
/** |
|
* Returns admin status of the caller player. |
|
* Disconnected players are never admins. |
|
* |
|
* Different from `IsAdmin()` since this method allows to distinguish between |
|
* different types of admin login (like silent admins). |
|
* |
|
* @return Admin status of the caller `EPlayer`. |
|
*/ |
|
public final function AdminStatus GetAdminStatus() |
|
{ |
|
local PlayerReplicationInfo myReplicationInfo; |
|
myReplicationInfo = GetRI(); |
|
if (myReplicationInfo == none) { |
|
return AS_None; |
|
} |
|
if (myReplicationInfo.bAdmin) { |
|
return AS_Admin; |
|
} |
|
if (myReplicationInfo.bSilentAdmin) { |
|
return AS_SilentAdmin; |
|
} |
|
return AS_None; |
|
} |
|
|
|
/** |
|
* Checks if caller player has admin rights. |
|
* Disconnected players never have admin rights. |
|
* |
|
* Different from `GetAdminStatus()` since this method simply checks admin |
|
* rights, without distinguishing between different types of admin login |
|
* (like silent admins). |
|
* |
|
* @return `true` if player has admin rights and `false` otherwise. |
|
*/ |
|
public final function bool IsAdmin() |
|
{ |
|
return (GetAdminStatus() != AS_None); |
|
} |
|
|
|
/** |
|
* Changes admin status of the caller `EPlayer`. |
|
* Can only fail if caller `EPlayer` has already disconnected. |
|
* |
|
* @param newAdminStatus New admin status of the `EPlayer`. |
|
*/ |
|
public final function SetAdminStatus(AdminStatus newAdminStatus) |
|
{ |
|
local PlayerReplicationInfo myReplicationInfo; |
|
myReplicationInfo = GetRI(); |
|
if (myReplicationInfo == none) { |
|
return; |
|
} |
|
switch (newAdminStatus) |
|
{ |
|
case AS_Admin: |
|
myReplicationInfo.bAdmin = true; |
|
myReplicationInfo.bSilentAdmin = false; |
|
break; |
|
case AS_SilentAdmin: |
|
myReplicationInfo.bAdmin = false; |
|
myReplicationInfo.bSilentAdmin = true; |
|
break; |
|
default: |
|
myReplicationInfo.bAdmin = false; |
|
myReplicationInfo.bSilentAdmin = false; |
|
} |
|
} |
|
|
|
/** |
|
* Returns current amount of money caller `EPlayer` has. |
|
* |
|
* @return Amount of money `EPlayer` has. If player has already disconnected |
|
* method will return `0`. |
|
*/ |
|
public final function int GetDosh() |
|
{ |
|
local PlayerReplicationInfo myReplicationInfo; |
|
myReplicationInfo = GetRI(); |
|
if (myReplicationInfo == none) { |
|
return 0; |
|
} |
|
return myReplicationInfo.score; |
|
} |
|
|
|
/** |
|
* Sets amount of money that caller `EPlayer` will have. |
|
* |
|
* @param newDoshAmount New amount of money that caller `EPlayer` must have. |
|
*/ |
|
public final function SetDosh(int newDoshAmount) |
|
{ |
|
local PlayerReplicationInfo myReplicationInfo; |
|
myReplicationInfo = GetRI(); |
|
if (myReplicationInfo == none) { |
|
return; |
|
} |
|
myReplicationInfo.score = newDoshAmount; |
|
} |
|
|
|
/** |
|
* Return `ConsoleWriter` that can be used to write into this 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 this player's |
|
* console. Returned object should not be deallocated, but it is |
|
* guaranteed to be valid for non-disconnected players. |
|
*/ |
|
public final function /* borrow */ ConsoleWriter BorrowConsole() |
|
{ |
|
if ( consoleInstance == none |
|
|| consoleInstance.GetLifeVersion() != consoleLifeVersion) |
|
{ |
|
consoleInstance = _.console.For(self); |
|
consoleLifeVersion = consoleInstance.GetLifeVersion(); |
|
} |
|
// Set us as target in case someone messed with this setting |
|
return consoleInstance.ForPlayer(self); |
|
} |
|
|
|
defaultproperties |
|
{ |
|
usesObjectPool = false |
|
} |