diff --git a/config/FutilityChat.ini b/config/FutilityChat.ini
new file mode 100644
index 0000000..4c26d6e
--- /dev/null
+++ b/config/FutilityChat.ini
@@ -0,0 +1,31 @@
+[default FutilityChat]
+; This feature allows to configure color of text chat messages.
+autoEnable=true
+; How to color text chat messages?
+; 1. `CCS_DoNothing` - do not change color in any way;
+; 2. `CCS_TeamColorForced` - force players' team colors for
+; their messages;
+; 3. `CCS_ConfigColorForced` - force `configuredColor` value for
+; players' messages;
+; 4. `CCS_TeamColorCustom` - use players' team colors for
+; their messages by default, but allow to change color with formatted
+; tags (e.g. "Stop right there, {$crimson criminal} scum!");
+; 5. `CCS_ConfigColorCustom` - use `configuredColor` value for
+; messages by default, but allow to change color with formatted
+; tags (e.g. "Stop right there, {$crimson criminal} scum!");
+; Default is `CCS_DoNothing`, corresponding to vanilla behaviour.
+colorSetting=CCS_DoNothing
+; Color that will be used if either of `CCS_ConfigColorForced` or
+; `CCS_ConfigColorCustom` options were used in `colorSetting`.
+; Default value is white: (R=255,G=255,B=255,A=255),
+; has no vanilla equivalent.
+configuredColor=(R=255,G=255,B=255,A=255)
+; Allows to modify team color's value for the chat messages
+; (if either of `CCS_TeamColorForced` or `CCS_TeamColorCustom` options
+; were used) to be lighter or darker.
+; This value is clamped between -1 and 1.
+; * `0` means using the same color;
+; * range (0; 1) - gives you lighter colors (`1` being white);
+; * range (-1; 0) - gives you darker colors (`-1` being black);
+; Default value is `0.6`, has no vanilla equivalent.
+teamColorModifier=0.6
\ No newline at end of file
diff --git a/sources/Features/FutileChat/FutilityChat.uc b/sources/Features/FutileChat/FutilityChat.uc
new file mode 100644
index 0000000..1d64110
--- /dev/null
+++ b/sources/Features/FutileChat/FutilityChat.uc
@@ -0,0 +1,92 @@
+/**
+ * Config object for `FutilityChat_Feature`.
+ * 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 .
+ */
+class FutilityChat extends FeatureConfig
+ perobjectconfig
+ config(FutilityChat);
+
+enum ChatColorSetting
+{
+ CCS_DoNothing,
+ CCS_TeamColorForced,
+ CCS_ConfigColorForced,
+ CCS_TeamColorCustom,
+ CCS_ConfigColorCustom
+};
+
+var public config ChatColorSetting colorSetting;
+var public config Color configuredColor;
+var public config float teamColorModifier;
+
+protected function AssociativeArray ToData()
+{
+ local AssociativeArray data;
+ data = __().collections.EmptyAssociativeArray();
+ data.SetItem( P("colorSetting"),
+ _.text.FromString(string(colorSetting)), true);
+ data.SetItem(P("configuredColor"), _.color.ToText(configuredColor), true);
+ data.SetFloat(P("teamColorModifier"), teamColorModifier, true);
+ return data;
+}
+
+protected function FromData(AssociativeArray source)
+{
+ if (source == none) {
+ return;
+ }
+ colorSetting = ColorSettingFromText(source.GetText(P("colorSetting")));
+ _.color.Parse(source.GetText(P("configuredColor")), configuredColor);
+ teamColorModifier = source.GetFloat(P("teamColorModifier"), 0.5);
+}
+
+private function ChatColorSetting ColorSettingFromText(
+ Text permissions)
+{
+ if (permissions == none) {
+ return CCS_DoNothing;
+ }
+ if (permissions.EndsWith(P("TeamColorForced"), SCASE_INSENSITIVE)) {
+ return CCS_TeamColorForced;
+ }
+ if (permissions.EndsWith(P("ConfigColorForced"), SCASE_INSENSITIVE)) {
+ return CCS_ConfigColorForced;
+ }
+ if (permissions.EndsWith(P("TeamColorCustom"), SCASE_INSENSITIVE)) {
+ return CCS_TeamColorCustom;
+ }
+ if (permissions.EndsWith(P("ConfigColorCustom"), SCASE_INSENSITIVE)) {
+ return CCS_ConfigColorCustom;
+ }
+ return CCS_DoNothing;
+}
+
+protected function DefaultIt()
+{;
+ colorSetting = CCS_DoNothing;
+ configuredColor = _.color.RGB(255, 255, 255);
+ teamColorModifier = 0.6;
+}
+
+defaultproperties
+{
+ configName = "FutilityChat"
+ colorSetting = CCS_DoNothing
+ configuredColor = (R=255,G=255,B=255,A=255)
+ teamColorModifier = 0.6
+}
\ No newline at end of file
diff --git a/sources/Features/FutileChat/FutilityChat_Feature.uc b/sources/Features/FutileChat/FutilityChat_Feature.uc
new file mode 100644
index 0000000..738feb2
--- /dev/null
+++ b/sources/Features/FutileChat/FutilityChat_Feature.uc
@@ -0,0 +1,166 @@
+/**
+ * This feature allows to configure color of text chat messages.
+ * 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 .
+ */
+class FutilityChat_Feature extends Feature
+ dependson(FutilityChat);
+
+// How to color text chat messages?
+// 1. `CCS_DoNothing` - do not change color in any way;
+// 2. `CCS_TeamColorForced` - force players' team colors for
+// their messages;
+// 3. `CCS_ConfigColorForced` - force `configuredColor` value for
+// players' messages;
+// 4. `CCS_TeamColorCustom` - use players' team colors for
+// their messages by default, but allow to change color with formatted
+// tags (e.g. "Stop right there, {$crimson criminal} scum!");
+// 5. `CCS_ConfigColorCustom` - use `configuredColor` value for
+// messages by default, but allow to change color with formatted
+// tags (e.g. "Stop right there, {$crimson criminal} scum!");
+// Default is `CCS_DoNothing`, corresponding to vanilla behaviour.
+var private /*config*/ FutilityChat.ChatColorSetting colorSetting;
+// Color that will be used if either of `CCS_ConfigColorForced` or
+// `CCS_ConfigColorCustom` options were used in `colorSetting`.
+// Default value is white: (R=255,G=255,B=255,A=255),
+// has no vanilla equivalent.
+var private /*config*/ Color configuredColor;
+// Allows to modify team color's value for the chat messages
+// (if either of `CCS_TeamColorForced` or `CCS_TeamColorCustom` options
+// were used) to be lighter or darker.
+// This value is clamped between -1 and 1.
+// * `0` means using the same color;
+// * range (0; 1) - gives you lighter colors (`1` being white);
+// * range (-1; 0) - gives you darker colors (`-1` being black);
+// Default value is `0.6`, has no vanilla equivalent.
+var private /*config*/ float teamColorModifier;
+
+// Keep track of whether we connected to necessary signals, so that we can
+// connect to them or disconnect from them once setting get updated
+var private bool connectedToSignal;
+
+protected function OnDisabled()
+{
+ if (connectedToSignal)
+ {
+ connectedToSignal = false;
+ _.chat.OnMessage(self).Disconnect();
+ }
+}
+
+protected function SwapConfig(FeatureConfig config)
+{
+ local bool configRequiresSignal;
+ local FutilityChat newConfig;
+ newConfig = FutilityChat(config);
+ if (newConfig == none) {
+ return;
+ }
+ colorSetting = newConfig.colorSetting;
+ configuredColor = newConfig.configuredColor;
+ teamColorModifier = newConfig.teamColorModifier;
+ configRequiresSignal = (colorSetting != CCS_DoNothing);
+ // Enable or disable censoring if `IsAnyCensoringEnabled()`'s response
+ // has changed.
+ if (!connectedToSignal && configRequiresSignal)
+ {
+ connectedToSignal = true;
+ _.chat.OnMessage(self).connect = ReformatChatMessage;
+ }
+ if (connectedToSignal && !configRequiresSignal)
+ {
+ connectedToSignal = false;
+ _.chat.OnMessage(self).Disconnect();
+ }
+}
+
+private function bool ReformatChatMessage(
+ EPlayer sender,
+ MutableText message,
+ bool teamMessage)
+{
+ local int i;
+ local MutableText messageCopy;
+ local Text.Character nextCharacter;
+ local Text.Formatting defaultFormatting;
+ if (sender == none) return true;
+ if (message == none) return true;
+ if (colorSetting == CCS_DoNothing) return true;
+
+ defaultFormatting.isColored = true;
+ if ( colorSetting == CCS_TeamColorForced
+ || colorSetting == CCS_TeamColorCustom)
+ {
+ defaultFormatting.color = ModColor(sender.GetTeamColor());
+ }
+ else {
+ defaultFormatting.color = configuredColor;
+ }
+ if (message.StartsWith(P("|"))) {
+ messageCopy = message.MutableCopy(1);
+ }
+ else if ( colorSetting == CCS_TeamColorForced
+ || colorSetting == CCS_ConfigColorForced)
+ {
+ messageCopy = message.MutableCopy();
+ }
+ else
+ {
+ messageCopy = _.text.Empty();
+ messageCopy.AppendFormatted(message);
+ }
+ message.Clear();
+ for (i = 0; i < messageCopy.GetLength(); i += 1)
+ {
+ nextCharacter = messageCopy.GetCharacter(i);
+ if (!nextCharacter.formatting.isColored) {
+ nextCharacter.formatting = defaultFormatting;
+ }
+ message.AppendCharacter(nextCharacter);
+ }
+ _.memory.Free(messageCopy);
+ return true;
+}
+
+private function Color ModColor(Color inputColor)
+{
+ local Color mixColor;
+ local Color outputColor;
+ local float clampedModifier;
+ if (Abs(teamColorModifier) < 0.001) {
+ return inputColor;
+ }
+ clampedModifier = FClamp(teamColorModifier, -1.0, 1.0);
+ if (clampedModifier > 0) {
+ mixColor = _.color.White;
+ }
+ else
+ {
+ mixColor = _.color.Black;
+ clampedModifier *= -1.0;
+ }
+ outputColor.R = Lerp(clampedModifier, inputColor.R, mixColor.R);
+ outputColor.G = Lerp(clampedModifier, inputColor.G, mixColor.G);
+ outputColor.B = Lerp(clampedModifier, inputColor.B, mixColor.B);
+ outputColor.A = inputColor.A;
+ return outputColor;
+}
+
+defaultproperties
+{
+ configClass = class'FutilityChat'
+}
\ No newline at end of file
diff --git a/sources/Manifest.uc b/sources/Manifest.uc
index e95f9d9..0f174cd 100644
--- a/sources/Manifest.uc
+++ b/sources/Manifest.uc
@@ -1,6 +1,6 @@
/**
* Manifest is meant to describe contents of the Acedia's package.
- * Copyright 2021 Anton Tarasenko
+ * Copyright 2021 - 2022 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@@ -24,4 +24,5 @@ defaultproperties
{
features(0) = class'Futility_Feature'
features(1) = class'FutilityNicknames_Feature'
+ features(2) = class'FutilityChat_Feature'
}
\ No newline at end of file