diff --git a/sources/Chat/ChatAPI.uc b/sources/Chat/ChatAPI.uc
new file mode 100644
index 0000000..ef56f0d
--- /dev/null
+++ b/sources/Chat/ChatAPI.uc
@@ -0,0 +1,156 @@
+/**
+ * API that provides functions for working with chat.
+ * 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 ChatAPI extends AcediaObject;
+
+var protected ChatAPI_OnMessage_Signal onMessageSignal;
+var protected ChatAPI_OnMessageFor_Signal onMessageForSignal;
+
+protected function Constructor()
+{
+ onMessageSignal = ChatAPI_OnMessage_Signal(
+ _.memory.Allocate(class'ChatAPI_OnMessage_Signal'));
+ onMessageForSignal = ChatAPI_OnMessageFor_Signal(
+ _.memory.Allocate(class'ChatAPI_OnMessageFor_Signal'));
+ _.unreal.broadcasts.OnHandleText(self).connect = HandleText;
+ _.unreal.broadcasts.OnHandleTextFor(self).connect = HandleTextFor;
+}
+
+protected function Finalizer()
+{
+ _.memory.Free(onMessageSignal);
+ _.memory.Free(onMessageForSignal);
+ onMessageSignal = none;
+ onMessageForSignal = none;
+ _.unreal.broadcasts.OnHandleText(self).Disconnect();
+ _.unreal.broadcasts.OnHandleTextFor(self).Disconnect();
+}
+
+/**
+ * Signal that will be emitted when a player sends a message into the chat.
+ * Allows to modify message before sending it, as well as prevent it from
+ * being sent at all.
+ *
+ * Return `false` to prevent message from being sent.
+ * If `false` is returned, signal propagation to the remaining handlers will
+ * also be interrupted.
+ *
+ * [Signature]
+ * bool (EPlayer sender, MutableText message, bool teamMessage)
+ *
+ * @param sender `EPlayer` that has sent the message.
+ * @param message Message that `sender` has sent. This is a mutable
+ * variable and can be modified from message will be sent.
+ * @param teamMessage Is this a team message
+ * (to be sent only to players on the same team)?
+ * @return Return `false` to prevent this message from being sent at all
+ * and `true` otherwise. Message will be sent only if all handlers will
+ * return `true`.
+ */
+/* SIGNAL */
+public function ChatAPI_OnMessage_Slot OnMessage(
+ AcediaObject receiver)
+{
+ return ChatAPI_OnMessage_Slot(onMessageSignal.NewSlot(receiver));
+}
+
+/**
+ * Signal that will be emitted when a player sends a message into the chat.
+ * Allows to modify message before sending it, as well as prevent it from
+ * being sent at all.
+ *
+ * Return `false` to prevent message from being sent to a specific player.
+ * If `false` is returned, signal propagation to the remaining handlers will
+ * also be interrupted.
+ *
+ * [Signature]
+ * bool (EPlayer receiver, EPlayer sender, Text message)
+ *
+ * @param receiver `EPlayer` that will receive the message.
+ * @param sender `EPlayer` that has sent the message.
+ * @param message Message that `sender` has sent. This is an immutable
+ * variable and cannot be changed at this point. Use `OnMessage()`
+ * signal function for that.
+ * @return Return `false` to prevent this message from being sent to
+ * a particular player and `true` otherwise. Message will be sent only if
+ * all handlers will return `true`.
+ * However decision whether to send message or not is made for
+ * every player separately.
+ */
+/* SIGNAL */
+public function ChatAPI_OnMessageFor_Slot OnMessageFor(
+ AcediaObject receiver)
+{
+ return ChatAPI_OnMessageFor_Slot(onMessageForSignal.NewSlot(receiver));
+}
+
+private function bool HandleText(
+ Actor sender,
+ out string message,
+ name messageType,
+ bool teamMessage)
+{
+ local bool result;
+ local MutableText messageAsText;
+ local EPlayer senderPlayer;
+ // We only want to catch chat messages from a player
+ if (messageType != 'Say' && messageType != 'TeamSay') return true;
+ senderPlayer = _.players.FromController(PlayerController(sender));
+ if (senderPlayer == none) return true;
+
+ messageAsText = __().text.FromColoredStringM(message);
+ result = onMessageSignal.Emit(senderPlayer, messageAsText, teamMessage);
+ message = messageAsText.ToColoredString();
+ _.memory.Free(messageAsText);
+ _.memory.Free(senderPlayer);
+ return result;
+}
+
+private function bool HandleTextFor(
+ PlayerController receiver,
+ Actor sender,
+ out string message,
+ name messageType)
+{
+ local bool result;
+ local Text messageAsText;
+ local EPlayer senderPlayer, receiverPlayer;
+ // We only want to catch chat messages from another player
+ if (messageType != 'Say' && messageType != 'TeamSay') return true;
+ senderPlayer = _.players.FromController(PlayerController(sender));
+ if (senderPlayer == none) return true;
+
+ receiverPlayer = _.players.FromController(receiver);
+ if (receiverPlayer == none)
+ {
+ _.memory.Free(senderPlayer);
+ return true;
+ }
+ messageAsText = __().text.FromColoredString(message);
+ result = onMessageForSignal.Emit( receiverPlayer, senderPlayer,
+ messageAsText);
+ _.memory.Free(messageAsText);
+ _.memory.Free(senderPlayer);
+ _.memory.Free(receiverPlayer);
+ return result;
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Chat/Events/ChatAPI_OnMessageFor_Signal.uc b/sources/Chat/Events/ChatAPI_OnMessageFor_Signal.uc
new file mode 100644
index 0000000..ff6dbb6
--- /dev/null
+++ b/sources/Chat/Events/ChatAPI_OnMessageFor_Signal.uc
@@ -0,0 +1,46 @@
+/**
+ * Signal class implementation for `ChatAPI`'s `OnMessageFor` 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 .
+ */
+class ChatAPI_OnMessageFor_Signal extends Signal;
+
+public final function bool Emit(EPlayer receiver, EPlayer sender, Text message)
+{
+ local Slot nextSlot;
+ local bool nextReply;
+ StartIterating();
+ nextSlot = GetNextSlot();
+ while (nextSlot != none)
+ {
+ nextReply = ChatAPI_OnMessageFor_Slot(nextSlot)
+ .connect(receiver, sender, message);
+ if (!nextReply && !nextSlot.IsEmpty())
+ {
+ CleanEmptySlots();
+ return false;
+ }
+ nextSlot = GetNextSlot();
+ }
+ CleanEmptySlots();
+ return true;
+}
+
+defaultproperties
+{
+ relatedSlotClass = class'ChatAPI_OnMessageFor_Slot'
+}
\ No newline at end of file
diff --git a/sources/Chat/Events/ChatAPI_OnMessageFor_Slot.uc b/sources/Chat/Events/ChatAPI_OnMessageFor_Slot.uc
new file mode 100644
index 0000000..bc921e4
--- /dev/null
+++ b/sources/Chat/Events/ChatAPI_OnMessageFor_Slot.uc
@@ -0,0 +1,41 @@
+/**
+ * Slot class implementation for `CharAPI`'s `OnMessageFor` 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 .
+ */
+class ChatAPI_OnMessageFor_Slot extends Slot;
+
+delegate bool connect(EPlayer receiver, EPlayer sender, Text message)
+{
+ DummyCall();
+ return true;
+}
+
+protected function Constructor()
+{
+ connect = none;
+}
+
+protected function Finalizer()
+{
+ super.Finalizer();
+ connect = none;
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Chat/Events/ChatAPI_OnMessage_Signal.uc b/sources/Chat/Events/ChatAPI_OnMessage_Signal.uc
new file mode 100644
index 0000000..0ee07f9
--- /dev/null
+++ b/sources/Chat/Events/ChatAPI_OnMessage_Signal.uc
@@ -0,0 +1,49 @@
+/**
+ * Signal class implementation for `ChatAPI`'s `OnMessage` 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 .
+ */
+class ChatAPI_OnMessage_Signal extends Signal;
+
+public final function bool Emit(
+ EPlayer sender,
+ MutableText message,
+ bool teamMessage)
+{
+ local Slot nextSlot;
+ local bool nextReply;
+ StartIterating();
+ nextSlot = GetNextSlot();
+ while (nextSlot != none)
+ {
+ nextReply = ChatAPI_OnMessage_Slot(nextSlot)
+ .connect(sender, message, teamMessage);
+ if (!nextReply && !nextSlot.IsEmpty())
+ {
+ CleanEmptySlots();
+ return false;
+ }
+ nextSlot = GetNextSlot();
+ }
+ CleanEmptySlots();
+ return true;
+}
+
+defaultproperties
+{
+ relatedSlotClass = class'ChatAPI_OnMessage_Slot'
+}
\ No newline at end of file
diff --git a/sources/Chat/Events/ChatAPI_OnMessage_Slot.uc b/sources/Chat/Events/ChatAPI_OnMessage_Slot.uc
new file mode 100644
index 0000000..6e4a48d
--- /dev/null
+++ b/sources/Chat/Events/ChatAPI_OnMessage_Slot.uc
@@ -0,0 +1,44 @@
+/**
+ * Slot class implementation for `ChatAPI`'s `OnMessage` 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 .
+ */
+class ChatAPI_OnMessage_Slot extends Slot;
+
+delegate bool connect(
+ EPlayer sender,
+ MutableText message,
+ bool teamMessage)
+{
+ DummyCall();
+ return true;
+}
+
+protected function Constructor()
+{
+ connect = none;
+}
+
+protected function Finalizer()
+{
+ super.Finalizer();
+ connect = none;
+}
+
+defaultproperties
+{
+}
\ No newline at end of file
diff --git a/sources/Commands/Commands_Feature.uc b/sources/Commands/Commands_Feature.uc
index 9760f36..665e044 100644
--- a/sources/Commands/Commands_Feature.uc
+++ b/sources/Commands/Commands_Feature.uc
@@ -38,7 +38,7 @@ protected function OnEnabled()
{
registeredCommands = _.collections.EmptyAssociativeArray();
RegisterCommand(class'ACommandHelp');
- _.unreal.broadcasts.OnHandleText(self).connect = HandleText;
+ _.chat.OnMessage(self).connect = HandleCommands;
// Macro selector
commandDelimiters[0] = P("@");
// Key selector
@@ -51,7 +51,7 @@ protected function OnEnabled()
protected function OnDisabled()
{
- _.unreal.broadcasts.OnHandleText(self).Disconnect();
+ _.chat.OnMessage(self).Disconnect();
if (registeredCommands != none)
{
registeredCommands.Empty(true);
@@ -221,35 +221,24 @@ public final function HandleInput(Parser parser, EPlayer callerPlayer)
}
}
-private function bool HandleText(
- Actor sender,
- out string message,
- name messageType,
+private function bool HandleCommands(
+ EPlayer sender,
+ MutableText message,
bool teamMessage)
{
- local Text messageAsText;
- local EPlayer callerPlayer;
- local Parser parser;
- // We only want to catch chat messages
- // and only if `Commands` feature is active
- if (messageType != 'Say') return true;
- if (!UsingChatInput()) return true;
+ local Parser parser;
+ if (!UsingChatInput()) {
+ return true;
+ }
// We are only interested in messages that start with "!"
- parser = __().text.ParseString(message);
+ parser = _.text.Parse(message);
if (!parser.Match(P("!")).Ok())
{
parser.FreeSelf();
- // Convert color tags into colors
- messageAsText = __().text.FromFormattedString(message);
- message = messageAsText.ToColoredString(,, __().color.White);
- messageAsText.FreeSelf();
return true;
}
- // Extract `EPlayer` from the `sender`
- callerPlayer = _.players.FromController(PlayerController(sender));
// Pass input to command feature
- HandleInput(parser, callerPlayer);
- _.memory.Free(callerPlayer);
+ HandleInput(parser, sender);
parser.FreeSelf();
return false;
}
diff --git a/sources/Global.uc b/sources/Global.uc
index ca6e9ee..4e10b91 100644
--- a/sources/Global.uc
+++ b/sources/Global.uc
@@ -35,6 +35,7 @@ var public AliasesAPI alias;
var public TextAPI text;
var public MemoryAPI memory;
var public ConsoleAPI console;
+var public ChatAPI chat;
var public ColorAPI color;
var public UserAPI users;
var public PlayersAPI players;
@@ -70,6 +71,7 @@ protected function Initialize()
logger = LoggerAPI(memory.Allocate(class'LoggerAPI'));
alias = AliasesAPI(memory.Allocate(class'AliasesAPI'));
console = ConsoleAPI(memory.Allocate(class'ConsoleAPI'));
+ chat = ChatAPI(memory.Allocate(class'ChatAPI'));
color = ColorAPI(memory.Allocate(class'ColorAPI'));
users = UserAPI(memory.Allocate(class'UserAPI'));
players = PlayersAPI(memory.Allocate(class'PlayersAPI'));
@@ -99,6 +101,7 @@ public function DropCoreAPI()
logger = none;
alias = none;
console = none;
+ chat = none;
color = none;
users = none;
players = none;