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.
267 lines
10 KiB
267 lines
10 KiB
/** |
|
* Config class for storing map lists. |
|
* Copyright 2022-2023 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 FutilityNicknames_Feature extends Feature |
|
dependson(FutilityNicknames); |
|
|
|
//! This feature's functionality is rather simple, but we will still break up |
|
//! what its various components are. |
|
//! |
|
//! Fallback nicknames are picked at random from |
|
//! the `fallbackNicknames` array. This is done by copying that array into |
|
//! `unusedNicknames` and then picking and removing its random elements each |
|
//! time we need a fallback. Once `unusedNicknames` is empty - it is copied from |
|
//! `fallbackNicknames` once again, letting already used nicknames to be reused. |
|
//! `unusedNicknames` contains same references as `fallbackNicknames`, |
|
//! so they need to be separately deallocated and should also be forgotten once |
|
//! `fallbackNicknames` are deallocated`. |
|
//! This is implemented inside `PickNextFallback()` method. |
|
//! |
|
//! Nickname changes are applied inside `CensorNickname()` method that uses |
|
//! several auxiliary methods to perform different stages of "censoring". |
|
//! Censoring is performed: |
|
//! 1. On any player's name change |
|
//! (using `OnPlayerNameChanging()` signal, connected to |
|
//! `HandleNicknameChange()`); |
|
//! 2. When new player logins (using `OnNewPlayer()` signal, |
|
//! conneted to `CensorOriginalNickname()`) to enforce our own |
|
//! handling of player's original nickname. |
|
|
|
var private /*config*/ FutilityNicknames.NicknameSpacesAction spacesAction; |
|
var private /*config*/ FutilityNicknames.NicknameColorPermissions colorPermissions; |
|
var private /*config*/ bool replaceSpacesWithUnderscores; |
|
var private /*config*/ bool removeSingleQuotationMarks; |
|
var private /*config*/ bool removeDoubleQuotationMarks; |
|
var private /*config*/ int maxNicknameLength; |
|
var private /*config*/ bool correctEmptyNicknames; |
|
var private /*config*/ array<Text> fallbackNickname; |
|
|
|
/// Guaranteed order of applying changes (only chosen ones) is as following: |
|
/// |
|
/// 1. Trim/simplify spaces; |
|
/// 2. Remove single and double quotation marks; |
|
/// 3. Enforce max limit of nickname's length; |
|
/// 4. Replace empty nickname with fallback nickname (no further changes |
|
/// will be applied to fallback nickname in that case); |
|
/// 5. Enforce color limitation; |
|
/// 6. Replace remaining whitespaces with underscores. |
|
/// |
|
/// NOTE #1: as follows from the instruction described above, no changes will |
|
/// ever be applied to fallback nicknames (unless player's nickname coincides |
|
/// with one by pure accident). |
|
/// |
|
/// NOTE #2: whitespaces inside steam nicknames are converted into underscores |
|
/// before they are passed into the game and this is a change Futility |
|
/// cannot currently abort. Therefore all changes relevant to whitespaces inside |
|
/// nicknames will only be applied to in-game changes. |
|
|
|
/// Nicknames from `fallbackNickname` that can still be picked in the current |
|
/// rotation. |
|
var private array<Text> unusedNicknames; |
|
|
|
var private const int CODEPOINT_UNDERSCORE; |
|
|
|
protected function OnEnabled() { |
|
if (IsAnyCensoringEnabled()) { |
|
// Do this before adding event handler to avoid censoring nicknames |
|
// second time (censoring nickname will trigger `OnPlayerNameChanging()` |
|
// signal) |
|
CensorCurrentPlayersNicknames(); |
|
_.players.OnPlayerNameChanging(self).connect = HandleNicknameChange; |
|
_.players.OnNewPlayer(self).connect = CensorOriginalNickname; |
|
} |
|
} |
|
|
|
protected function OnDisabled() { |
|
_.memory.FreeMany(fallbackNickname); |
|
_.memory.FreeMany(unusedNicknames); |
|
fallbackNickname.length = 0; |
|
unusedNicknames.length = 0; |
|
if (IsAnyCensoringEnabled()) { |
|
_.players.OnPlayerNameChanging(self).Disconnect(); |
|
_.players.OnNewPlayer(self).Disconnect(); |
|
} |
|
} |
|
|
|
protected function SwapConfig(FeatureConfig config) { |
|
local FutilityNicknames newConfig; |
|
|
|
newConfig = FutilityNicknames(config); |
|
if (newConfig == none) { |
|
return; |
|
} |
|
replaceSpacesWithUnderscores = newConfig.replaceSpacesWithUnderscores; |
|
removeSingleQuotationMarks = newConfig.removeSingleQuotationMarks; |
|
removeDoubleQuotationMarks = newConfig.removeDoubleQuotationMarks; |
|
correctEmptyNicknames = newConfig.correctEmptyNicknames; |
|
spacesAction = newConfig.spacesAction; |
|
colorPermissions = newConfig.colorPermissions; |
|
maxNicknameLength = newConfig.maxNicknameLength; |
|
SwapFallbackNicknames(newConfig); |
|
} |
|
|
|
private function SwapFallbackNicknames(FutilityNicknames newConfig) { |
|
local int i; |
|
|
|
_.memory.FreeMany(fallbackNickname); |
|
fallbackNickname.length = 0; |
|
for (i = 0; i < newConfig.fallbackNickname.length; i += 1) { |
|
fallbackNickname[i] = _.text.FromFormattedString(newConfig.fallbackNickname[i]); |
|
} |
|
unusedNicknames = fallbackNickname; |
|
} |
|
|
|
private function Text PickNextFallback() { |
|
local int pickedIndex; |
|
local Text result; |
|
|
|
if (fallbackNickname.length <= 0) { |
|
// Just in case this feature is really misconfigured |
|
return P("Fresh Meat").Copy(); |
|
} |
|
if (unusedNicknames.length <= 0) { |
|
unusedNicknames = fallbackNickname; |
|
} |
|
// Pick one nickname at random. |
|
// `pickedIndex` will belong to [0; unusedNicknames.length - 1] segment. |
|
pickedIndex = Rand(unusedNicknames.length); |
|
result = unusedNicknames[pickedIndex].Copy(); |
|
unusedNicknames.Remove(pickedIndex, 1); |
|
return result; |
|
} |
|
|
|
private function bool IsAnyCensoringEnabled() { |
|
return ( replaceSpacesWithUnderscores |
|
|| removeSingleQuotationMarks |
|
|| removeDoubleQuotationMarks |
|
|| correctEmptyNicknames |
|
|| maxNicknameLength >= 0 |
|
|| colorPermissions != NCP_AllowAnyColor |
|
|| spacesAction != NSA_DoNothing); |
|
} |
|
|
|
// For nickname changes mid-game. |
|
private function HandleNicknameChange( |
|
EPlayer affectedPlayer, |
|
BaseText oldName, |
|
MutableText newName |
|
) { |
|
CensorNickname(newName, affectedPlayer); |
|
} |
|
|
|
// For handling of player's original nicknames. |
|
private function CensorOriginalNickname(EPlayer affectedPlayer) { |
|
local Text originalNickname; |
|
if (affectedPlayer == none) { |
|
return; |
|
} |
|
originalNickname = affectedPlayer.GetOriginalName(); |
|
// This will automatically trigger `OnPlayerNameChanging()` signal and |
|
// our `HandleNicknameChange()` handler. |
|
affectedPlayer.SetName(originalNickname); |
|
_.memory.Free(originalNickname); |
|
} |
|
|
|
// For handling nicknames of players after censoring is re-activated by |
|
// config change. |
|
private function CensorCurrentPlayersNicknames() { |
|
local int i; |
|
local Text nextNickname; |
|
local MutableText alteredNickname; |
|
local array<EPlayer> currentPlayers; |
|
|
|
currentPlayers = _.players.GetAll(); |
|
for (i = 0; i < currentPlayers.length; i += 1) { |
|
nextNickname = currentPlayers[i].GetName(); |
|
alteredNickname = nextNickname.MutableCopy(); |
|
CensorNickname(alteredNickname, currentPlayers[i]); |
|
if (!alteredNickname.Compare(nextNickname)) { |
|
currentPlayers[i].SetName(alteredNickname); |
|
} |
|
_.memory.Free(alteredNickname); |
|
_.memory.Free(nextNickname); |
|
} |
|
} |
|
|
|
private function CensorNickname(MutableText nickname, EPlayer affectedPlayer) { |
|
local Text fallback; |
|
local BaseText.Formatting newFormatting; |
|
|
|
if (nickname == none) return; |
|
if (affectedPlayer == none) return; |
|
|
|
if (spacesAction != NSA_DoNothing) { |
|
nickname.Simplify(spacesAction == NSA_Simplify); |
|
} |
|
if (removeSingleQuotationMarks) { |
|
nickname.Replace(P("'"), P("")); |
|
} |
|
if (removeDoubleQuotationMarks) { |
|
nickname.Replace(P("\""), P("")); |
|
} |
|
if (maxNicknameLength >= 0) { |
|
nickname.Remove(maxNicknameLength); |
|
} |
|
if (correctEmptyNicknames && nickname.IsEmpty()) { |
|
fallback = PickNextFallback(); |
|
nickname.Append(fallback); |
|
_.memory.Free(fallback); |
|
return; |
|
} |
|
if (colorPermissions != NCP_AllowAnyColor) { |
|
if (colorPermissions == NCP_ForceSingleColor) { |
|
newFormatting = nickname.GetCharacter(0).formatting; |
|
} else if (colorPermissions == NCP_ForceTeamColor) { |
|
newFormatting.isColored = true; |
|
newFormatting.color = affectedPlayer.GetTeamColor(); |
|
} |
|
// `colorPermissions == NCP_ForbidColor` |
|
// `newFormatting` is colorless by default |
|
nickname.ChangeFormatting(newFormatting); |
|
} |
|
if (replaceSpacesWithUnderscores) { |
|
ReplaceSpaces(nickname); |
|
} |
|
} |
|
|
|
// Asusmes `nickname != none`. |
|
private function ReplaceSpaces(MutableText nickname) { |
|
local int i; |
|
local MutableText nicknameCopy; |
|
local BaseText.Character nextCharacter, underscoreCharacter; |
|
|
|
nicknameCopy = nickname.MutableCopy(); |
|
nickname.Clear(); |
|
underscoreCharacter = _.text.CharacterFromCodePoint(CODEPOINT_UNDERSCORE); |
|
for (i = 0; i < nicknameCopy.GetLength(); i += 1) { |
|
nextCharacter = nicknameCopy.GetCharacter(i); |
|
if (_.text.IsWhitespace(nextCharacter)) { |
|
// Replace character with underscore, leaving the formatting |
|
underscoreCharacter.formatting = nextCharacter.formatting; |
|
nextCharacter = underscoreCharacter; |
|
} |
|
nickname.AppendCharacter(nextCharacter); |
|
} |
|
_.memory.Free(nicknameCopy); |
|
} |
|
|
|
defaultproperties { |
|
configClass = class'FutilityNicknames' |
|
CODEPOINT_UNDERSCORE = 95 // '_' |
|
} |