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;