Browse Source

Add signals for player's name change

pull/8/head
Anton Tarasenko 3 years ago
parent
commit
6907b53a9c
  1. 115
      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. 63
      sources/Players/PlayersAPI.uc

115
sources/Players/EPlayer.uc

@ -30,12 +30,9 @@ var private int consoleLifeVersion;
// `PlayerController` reference // `PlayerController` reference
var private NativeActorRef controller; var private NativeActorRef controller;
// These variables record name of this player; /**
// `hashedName` is used to track outside changes that bypass our getter/setter. * Describes the player's admin status (as defined by standard KF classes)
var private Text textName; */
var private string hashedName;
// Describes the player's admin status (as defined by standard KF classes)
enum AdminStatus enum AdminStatus
{ {
// Not an admin // Not an admin
@ -46,6 +43,16 @@ enum AdminStatus
AS_SilentAdmin 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() protected function Finalizer()
{ {
_.memory.Free(controller); _.memory.Free(controller);
@ -74,10 +81,10 @@ protected function Finalizer()
* @return `true` if initialization was successful and `false` otherwise. * @return `true` if initialization was successful and `false` otherwise.
*/ */
public final /* unreal */ function bool Initialize( public final /* unreal */ function bool Initialize(
PlayerController initController) PlayerController initController,
PlayerSignals playerSignals)
{ {
local Text idHash; local Text idHash;
local PlayerReplicationInfo myReplicationInfo;
if (controller != none) return false; // Already initialized! if (controller != none) return false; // Already initialized!
if (initController == none) return false; if (initController == none) return false;
@ -91,14 +98,8 @@ public final /* unreal */ function bool Initialize(
idHash.FreeSelf(); idHash.FreeSelf();
idHash = none; idHash = none;
} }
signalsReferences = playerSignals;
controller = _.unreal.ActorRef(initController); controller = _.unreal.ActorRef(initController);
myReplicationInfo = initController.playerReplicationInfo;
// Hash current name
if (myReplicationInfo != none)
{
hashedName = myReplicationInfo.playerName;
textName = _.text.FromColoredString(hashedName);
}
return true; return true;
} }
@ -121,7 +122,8 @@ public function EInterface Copy()
return playerCopy; return playerCopy;
} }
playerCopy.identity = identity; playerCopy.identity = identity;
playerCopy.Initialize(PlayerController(controller.Get())); playerCopy.Initialize( PlayerController(controller.Get()),
signalsReferences);
return playerCopy; return playerCopy;
} }
@ -216,16 +218,10 @@ public final function Text GetName()
{ {
local PlayerReplicationInfo myReplicationInfo; local PlayerReplicationInfo myReplicationInfo;
myReplicationInfo = GetRI(); myReplicationInfo = GetRI();
if (myReplicationInfo == none) { if (myReplicationInfo != none) {
return P("").Copy(); return _.text.FromColoredString(myReplicationInfo.playerName);
}
if (textName != none && myReplicationInfo.playerName == hashedName) {
return textName.Copy();
} }
_.memory.Free(textName); return P("").Copy();
hashedName = myReplicationInfo.playerName;
textName = _.text.FromColoredString(hashedName);
return textName.Copy();
} }
/** /**
@ -236,40 +232,73 @@ public final function Text GetName()
*/ */
public final function SetName(Text newPlayerName) public final function SetName(Text newPlayerName)
{ {
local Text.Formatting endingFormatting; local Text oldPlayerName;
local PlayerReplicationInfo myReplicationInfo; local PlayerReplicationInfo replicationInfo;
myReplicationInfo = GetRI(); replicationInfo = GetRI();
if (myReplicationInfo == none) return; if (replicationInfo == 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();
} }
else { if (ConvertTextNameIntoString(newPlayerName) == replicationInfo.playerName)
textName = newPlayerName.Copy(); {
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 "";
} }
hashedName = textName.ToColoredString(,, _.color.white); newPlayerNameAsString = playerName.ToColoredString(,, _.color.white);
// To correctly display nicknames we want to drop default color tag // To correctly display nicknames we want to drop default color tag
// at the beginning (the one `ToColoredString()` adds if first character // at the beginning (the one `ToColoredString()` adds if first character
// has no defined color). // has no defined color).
// This is a compatibility consideration with vanilla UIs that use // This is a compatibility consideration with vanilla UIs that use
// color codes from `myReplicationInfo.playerName` for displaying nicknames // color codes from `myReplicationInfo.playerName` for displaying nicknames
// and whose expected behavior can get broken by default color tag. // and whose expected behavior can get broken by default color tag.
if (!textName.GetFormatting(0).isColored) { if (!playerName.GetFormatting(0).isColored) {
hashedName = Mid(hashedName, 4); newPlayerNameAsString = Mid(newPlayerNameAsString, 4);
} }
// This is another compatibility consideration with vanilla UIs: unless // This is another compatibility consideration with vanilla UIs: unless
// we restore color to neutral white, Killing Floor will paint any chat // we restore color to neutral white, Killing Floor will paint any chat
// messages we send in the color our nickname ended with. // 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 if ( endingFormatting.isColored
&& !_.color.AreEqual(endingFormatting.color, _.color.white, true)) && !_.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 // 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
{
}

63
sources/Players/PlayersAPI.uc

@ -1,6 +1,6 @@
/** /**
* API that provides functions for working player references (`EPlayer`). * API that provides functions for working player references (`EPlayer`).
* Copyright 2021 Anton Tarasenko * Copyright 2021 - 2022 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -19,7 +19,8 @@
*/ */
class PlayersAPI extends AcediaObject class PlayersAPI extends AcediaObject
dependson(ConnectionService) dependson(ConnectionService)
dependson(Text); dependson(Text)
dependson(EPlayer);
// Writer that can be used to write into this player's console // Writer that can be used to write into this player's console
var private ConsoleWriter consoleInstance; var private ConsoleWriter consoleInstance;
@ -30,12 +31,18 @@ var protected bool connectedToConnectionServer;
var protected PlayerAPI_OnNewPlayer_Signal onNewPlayerSignal; var protected PlayerAPI_OnNewPlayer_Signal onNewPlayerSignal;
var protected PlayerAPI_OnLostPlayer_Signal onLostPlayerSignal; var protected PlayerAPI_OnLostPlayer_Signal onLostPlayerSignal;
var private EPlayer.PlayerSignals playerSignals;
protected function Constructor() protected function Constructor()
{ {
onNewPlayerSignal = PlayerAPI_OnNewPlayer_Signal( onNewPlayerSignal = PlayerAPI_OnNewPlayer_Signal(
_.memory.Allocate(class'PlayerAPI_OnNewPlayer_Signal')); _.memory.Allocate(class'PlayerAPI_OnNewPlayer_Signal'));
onLostPlayerSignal = PlayerAPI_OnLostPlayer_Signal( onLostPlayerSignal = PlayerAPI_OnLostPlayer_Signal(
_.memory.Allocate(class'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() protected function Finalizer()
@ -50,8 +57,12 @@ protected function Finalizer()
} }
_.memory.Free(onNewPlayerSignal); _.memory.Free(onNewPlayerSignal);
_.memory.Free(onLostPlayerSignal); _.memory.Free(onLostPlayerSignal);
_.memory.Free(playerSignals.onNameChanging);
_.memory.Free(playerSignals.onNameChanged);
onNewPlayerSignal = none; onNewPlayerSignal = none;
onLostPlayerSignal = 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)); 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 * Return `ConsoleWriter` that can be used to write into every player's
* console. * console.
@ -163,7 +220,7 @@ public final /* unreal */ function EPlayer FromController(
{ {
local EPlayer result; local EPlayer result;
result = EPlayer(_.memory.Allocate(class'EPlayer')); result = EPlayer(_.memory.Allocate(class'EPlayer'));
result.Initialize(controller); result.Initialize(controller, playerSignals);
return result; return result;
} }

Loading…
Cancel
Save