Browse Source

Add signals for player's name change

pull/8/head
Anton Tarasenko 3 years ago
parent
commit
6907b53a9c
  1. 119
      sources/Players/EPlayer.uc
  2. 39
      sources/Players/Events/PlayerAPI_OnPlayerNameChanged_Signal.uc
  3. 40
      sources/Players/Events/PlayerAPI_OnPlayerNameChanged_Slot.uc
  4. 39
      sources/Players/Events/PlayerAPI_OnPlayerNameChanging_Signal.uc
  5. 40
      sources/Players/Events/PlayerAPI_OnPlayerNameChanging_Slot.uc
  6. 67
      sources/Players/PlayersAPI.uc

119
sources/Players/EPlayer.uc

@ -30,12 +30,9 @@ var private int consoleLifeVersion;
// `PlayerController` reference
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;
var private string hashedName;
// Describes the player's admin status (as defined by standard KF classes)
/**
* Describes the player's admin status (as defined by standard KF classes)
*/
enum AdminStatus
{
// Not an admin
@ -46,6 +43,16 @@ enum AdminStatus
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);
@ -74,10 +81,10 @@ protected function Finalizer()
* @return `true` if initialization was successful and `false` otherwise.
*/
public final /* unreal */ function bool Initialize(
PlayerController initController)
PlayerController initController,
PlayerSignals playerSignals)
{
local Text idHash;
local PlayerReplicationInfo myReplicationInfo;
local Text idHash;
if (controller != none) return false; // Already initialized!
if (initController == none) return false;
@ -91,14 +98,8 @@ public final /* unreal */ function bool Initialize(
idHash.FreeSelf();
idHash = none;
}
controller = _.unreal.ActorRef(initController);
myReplicationInfo = initController.playerReplicationInfo;
// Hash current name
if (myReplicationInfo != none)
{
hashedName = myReplicationInfo.playerName;
textName = _.text.FromColoredString(hashedName);
}
signalsReferences = playerSignals;
controller = _.unreal.ActorRef(initController);
return true;
}
@ -121,7 +122,8 @@ public function EInterface Copy()
return playerCopy;
}
playerCopy.identity = identity;
playerCopy.Initialize(PlayerController(controller.Get()));
playerCopy.Initialize( PlayerController(controller.Get()),
signalsReferences);
return playerCopy;
}
@ -216,16 +218,10 @@ public final function Text GetName()
{
local PlayerReplicationInfo myReplicationInfo;
myReplicationInfo = GetRI();
if (myReplicationInfo == none) {
return P("").Copy();
if (myReplicationInfo != none) {
return _.text.FromColoredString(myReplicationInfo.playerName);
}
if (textName != none && myReplicationInfo.playerName == hashedName) {
return textName.Copy();
}
_.memory.Free(textName);
hashedName = myReplicationInfo.playerName;
textName = _.text.FromColoredString(hashedName);
return textName.Copy();
return P("").Copy();
}
/**
@ -236,40 +232,73 @@ public final function Text GetName()
*/
public final function SetName(Text newPlayerName)
{
local Text.Formatting endingFormatting;
local PlayerReplicationInfo myReplicationInfo;
myReplicationInfo = GetRI();
if (myReplicationInfo == none) return;
_.memory.Free(textName);
// Filter both `none` and empty `newPlayerName`, so that we can
// later rely on it having at least one character
if (newPlayerName == none || newPlayerName.IsEmpty()) {
textName = P("").Copy();
local Text oldPlayerName;
local PlayerReplicationInfo replicationInfo;
replicationInfo = GetRI();
if (replicationInfo == none) {
return;
}
if (ConvertTextNameIntoString(newPlayerName) == replicationInfo.playerName)
{
return;
}
else {
textName = newPlayerName.Copy();
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 "";
}
hashedName = textName.ToColoredString(,, _.color.white);
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 (!textName.GetFormatting(0).isColored) {
hashedName = Mid(hashedName, 4);
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 = textName.GetFormatting(textName.GetLength() - 1);
endingFormatting = playerName.GetFormatting(playerName.GetLength() - 1);
if ( endingFormatting.isColored
&& !_.color.AreEqual(endingFormatting.color, _.color.white, true))
{
hashedName $= _.color.GetColorTag(_.color.white);
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 "";
}
myReplicationInfo.playerName = hashedName;
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

39
sources/Players/Events/PlayerAPI_OnPlayerNameChanged_Signal.uc

@ -0,0 +1,39 @@
/**
* Signal class implementation for `PlayerAPI`'s `OnPlayerNameChanged` signal.
* Copyright 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 PlayerAPI_OnPlayerNameChanged_Signal extends Signal;
public final function Emit(EPlayer player, Text oldName, Text newName)
{
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
PlayerAPI_OnPlayerNameChanged_Slot(nextSlot)
.connect(player, oldName, newName);
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
defaultproperties
{
relatedSlotClass = class'PlayerAPI_OnPlayerNameChanged_Slot'
}

40
sources/Players/Events/PlayerAPI_OnPlayerNameChanged_Slot.uc

@ -0,0 +1,40 @@
/**
* Slot class implementation for `PlayerAPI`'s `OnPlayerNameChanged` signal.
* Copyright 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 PlayerAPI_OnPlayerNameChanged_Slot extends Slot;
delegate connect(EPlayer player, Text oldName, Text newName)
{
DummyCall();
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

39
sources/Players/Events/PlayerAPI_OnPlayerNameChanging_Signal.uc

@ -0,0 +1,39 @@
/**
* Signal class implementation for `PlayerAPI`'s `OnPlayerNameChanging` signal.
* Copyright 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 PlayerAPI_OnPlayerNameChanging_Signal extends Signal;
public final function Emit(EPlayer player, Text oldName, MutableText newName)
{
local Slot nextSlot;
StartIterating();
nextSlot = GetNextSlot();
while (nextSlot != none)
{
PlayerAPI_OnPlayerNameChanging_Slot(nextSlot)
.connect(player, oldName, newName);
nextSlot = GetNextSlot();
}
CleanEmptySlots();
}
defaultproperties
{
relatedSlotClass = class'PlayerAPI_OnPlayerNameChanging_Slot'
}

40
sources/Players/Events/PlayerAPI_OnPlayerNameChanging_Slot.uc

@ -0,0 +1,40 @@
/**
* Slot class implementation for `PlayerAPI`'s `OnPlayerNameChanging` signal.
* Copyright 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 PlayerAPI_OnPlayerNameChanging_Slot extends Slot;
delegate connect(EPlayer player, Text oldName, MutableText newName)
{
DummyCall();
}
protected function Constructor()
{
connect = none;
}
protected function Finalizer()
{
super.Finalizer();
connect = none;
}
defaultproperties
{
}

67
sources/Players/PlayersAPI.uc

@ -1,6 +1,6 @@
/**
* API that provides functions for working player references (`EPlayer`).
* Copyright 2021 Anton Tarasenko
* Copyright 2021 - 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@ -19,7 +19,8 @@
*/
class PlayersAPI extends AcediaObject
dependson(ConnectionService)
dependson(Text);
dependson(Text)
dependson(EPlayer);
// Writer that can be used to write into this player's console
var private ConsoleWriter consoleInstance;
@ -30,12 +31,18 @@ var protected bool connectedToConnectionServer;
var protected PlayerAPI_OnNewPlayer_Signal onNewPlayerSignal;
var protected PlayerAPI_OnLostPlayer_Signal onLostPlayerSignal;
var private EPlayer.PlayerSignals playerSignals;
protected function Constructor()
{
onNewPlayerSignal = PlayerAPI_OnNewPlayer_Signal(
_.memory.Allocate(class'PlayerAPI_OnNewPlayer_Signal'));
onLostPlayerSignal = PlayerAPI_OnLostPlayer_Signal(
_.memory.Allocate(class'PlayerAPI_OnLostPlayer_Signal'));
playerSignals.onNameChanging = PlayerAPI_OnPlayerNameChanging_Signal(
_.memory.Allocate(class'PlayerAPI_OnPlayerNameChanging_Signal'));
playerSignals.onNameChanged = PlayerAPI_OnPlayerNameChanged_Signal(
_.memory.Allocate(class'PlayerAPI_OnPlayerNameChanged_Signal'));
}
protected function Finalizer()
@ -50,8 +57,12 @@ protected function Finalizer()
}
_.memory.Free(onNewPlayerSignal);
_.memory.Free(onLostPlayerSignal);
onNewPlayerSignal = none;
onLostPlayerSignal = none;
_.memory.Free(playerSignals.onNameChanging);
_.memory.Free(playerSignals.onNameChanged);
onNewPlayerSignal = none;
onLostPlayerSignal = none;
playerSignals.onNameChanging = none;
playerSignals.onNameChanged = none;
}
/**
@ -89,6 +100,52 @@ public function PlayerAPI_OnLostPlayer_Slot OnLostPlayerHandle(
return PlayerAPI_OnLostPlayer_Slot(onLostPlayerSignal.NewSlot(receiver));
}
/**
* Signal that will be emitted once player's name attempt to change.
*
* This signal gives all handlers a change to modify mutable `newName`,
* so the one you are given as a parameter might not be final, since other
* handlers can modify it after you.
* If you simply need to see the final version of the changed name -
* use `OnPlayerNameChanged` instead.
*
* [Signature]
* void <slot>(EPlayer affectedPlayer, Text oldName, MutableText newName)
*
* @param affectedPlayer Player, whos name got changed.
* @param oldName Player's old name.
* @param newName Player's new name. Can be modified, if you want
* to make corrections.
*/
/* SIGNAL */
public function PlayerAPI_OnPlayerNameChanging_Slot OnPlayerNameChanging(
AcediaObject receiver)
{
return PlayerAPI_OnPlayerNameChanging_Slot(playerSignals.onNameChanging
.NewSlot(receiver));
}
/**
* Signal that will be emitted once player's name is changed.
*
* This signal simply notifies you of the changed name, if you wish to alter it
* after change caused by someone else, use `OnPlayerNameChanging` instead.
*
* [Signature]
* void <slot>(EPlayer affectedPlayer, Text oldName, Text newName)
*
* @param affectedPlayer Player, whos name got changed.
* @param oldName Player's old name.
* @param newName Player's new name.
*/
/* SIGNAL */
public function PlayerAPI_OnPlayerNameChanged_Slot OnPlayerNameChanged(
AcediaObject receiver)
{
return PlayerAPI_OnPlayerNameChanged_Slot(playerSignals.onNameChanged
.NewSlot(receiver));
}
/**
* Return `ConsoleWriter` that can be used to write into every player's
* console.
@ -163,7 +220,7 @@ public final /* unreal */ function EPlayer FromController(
{
local EPlayer result;
result = EPlayer(_.memory.Allocate(class'EPlayer'));
result.Initialize(controller);
result.Initialize(controller, playerSignals);
return result;
}

Loading…
Cancel
Save