diff --git a/sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc b/sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc
index c24e018..79fe238 100644
--- a/sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc
+++ b/sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc
@@ -19,7 +19,8 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class ACommandNotify extends Command;
+class ACommandNotify extends Command
+ dependsOn(ChatApi);
protected function BuildData(CommandDataBuilder builder) {
builder.Name(P("notify"));
diff --git a/sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc b/sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc
index 0d0e674..ccb5206 100644
--- a/sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc
+++ b/sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc
@@ -1,6 +1,6 @@
/**
* Set of tests for `Command` class.
- * Copyright 2021 - 2022 Anton Tarasenko
+ * Copyright 2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
diff --git a/sources/BaseAPI/API/Unflect/UnflectApi.uc b/sources/BaseAPI/API/Unflect/UnflectApi.uc
index a41e791..ed89d07 100644
--- a/sources/BaseAPI/API/Unflect/UnflectApi.uc
+++ b/sources/BaseAPI/API/Unflect/UnflectApi.uc
@@ -306,6 +306,7 @@ private final function bool _replaceFunction(Text oldFunctionLowerCase, Text new
_.memory.Free(initialCode);
}
replace.script = with.script;
+ Log("TRULLY!" @ replace @ with);
return true;
}
diff --git a/sources/Chat/ChatAPI.uc b/sources/Chat/ChatAPI.uc
index b8601bc..3864297 100644
--- a/sources/Chat/ChatAPI.uc
+++ b/sources/Chat/ChatAPI.uc
@@ -1,6 +1,8 @@
/**
- * API that provides functions for working with chat.
- * Copyright 2022 Anton Tarasenko
+ * Author: dkanus
+ * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
+ * License: GPL
+ * Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------
* This file is part of Acedia.
*
@@ -17,112 +19,342 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class ChatAPI extends AcediaObject;
+class ChatApi extends AcediaObject;
+///! API for accessing chat-related events.
+///!
+///! # Implementation
+///!
+///! Signal functions that track text chat messages `OnMessage()` and `OnMessageFor()` simply
+///! hook into [`BroadcastApi`] the first time such signal is requested.
+///!
+///! Signal function [`OnVoiceMessage()`] for tracking voice replaces a function in
+///! [`KFPlayerController`] to track when they are replicated.
+///! Then replaced function informs [`ChatApi`] about new voice message transmissions via
+///! internal [`_EmitOnVoiceMessage()`] method.
+
+/// Lists voice messages built-in in the game.
+enum BuiltInVoiceMessage {
+ // Support
+ BIVM_SupportMedic,
+ BIVM_SupportHelp,
+ BIVM_SupportAskForMoney,
+ BIVM_SupportAskForWeapon,
+ // Acknowledgements
+ BIVM_AckYes,
+ BIVM_AckNo,
+ BIVM_AckThanks,
+ BIVM_AckSorry,
+ // Alert
+ BIVM_AlretLookOut,
+ BIVM_AlretRun,
+ BIVM_AlretWaitForMe,
+ BIVM_AlretWeldTheDoors,
+ BIVM_AlretLetsHoleUpHere,
+ BIVM_AlretFollowMe,
+ // Direction
+ BIVM_DirectionGetToTheTrader,
+ BIVM_DirectionGoUpstairs,
+ BIVM_DirectionGoDownstairs,
+ BIVM_DirectionGetOutside,
+ BIVM_DirectionGetInside,
+ // Insult
+ BIVM_InsultSpecimens,
+ BIVM_InsultPlayers,
+ // Trader
+ BIVM_TraderCheckWhereTheShopIs,
+ BIVM_TraderGetClose,
+ BIVM_TraderShopOpen,
+ BIVM_TraderShopOpenFinal,
+ BIVM_Trader30SecondsUntilShopCloses,
+ BIVM_TraderShopClosed,
+ BIVM_TraderCompliment,
+ BIVM_TraderNotEnoughMoney,
+ BIVM_TraderCannotCarry,
+ BIVM_TraderHurryUp1,
+ BIVM_TraderHurryUp2,
+ // Auto
+ BIVM_AutoWelding,
+ BIVM_AutoUnwelding,
+ BIVM_AutoReload,
+ BIVM_AutoOutOfAmmo,
+ BIVM_AutoDosh,
+ BIVM_AutoStandStillTryingToHealYou,
+ BIVM_AutoLowOnHealth,
+ BIVM_AutoBloatAcid,
+ BIVM_AutoPatriarchCloack,
+ BIVM_AutoPatriarchMinigun,
+ BIVM_AutoPatriarchRocketLauncher,
+ BIVM_AutoGrabbedByClot,
+ BIVM_AutoFleshpoundSpotted,
+ BIVM_AutoGorefastSpotted,
+ BIVM_AutoScrakeSpotted,
+ BIVM_AutoSirenSpotten,
+ BIVM_AutoSirenScream,
+ BIVM_AutoStalkerSpotted,
+ BIVM_AutoCrawlertSpotted,
+ BIVM_AutoMeleeKilledAStalker,
+ BIVM_AutoUsingFlamethrower,
+ BIVM_AutoEquipHuntingShotgun,
+ BIVM_AutoEquipHandcannons,
+ BIVM_AutoEquipLAW,
+ BIVM_AutoEquipFireaxe,
+ // Fallback
+ BIVM_Unknown
+};
+
+/// Killing Floor's native voice message is defined by `name` and `byte` pair.
+/// This struct is added to allow returning them as a pair.
+struct NativeVoiceMessage {
+ var name type;
+ var byte index;
+};
+
+/// Tracks whether we've already connected to broadcast signals.
var protected bool connectedToBroadcastAPI;
+/// Tracks whether we've already replaced a function that allows us to catch voice messages.
+var private bool replacedSendVoiceMessage;
-var protected ChatAPI_OnMessage_Signal onMessageSignal;
-var protected ChatAPI_OnMessageFor_Signal onMessageForSignal;
+/// Auxiliary constants that store amount of values in [`BuiltInVoiceMessage`] before
+/// a certain group.
+/// Used for conversion between native voice messages and [`BuiltInVoiceMessage`]
+var private const int VOICE_MESSAGES_BEFORE_ACKNOWLEDGEMENTS;
+var private const int VOICE_MESSAGES_BEFORE_ALERTS;
+var private const int VOICE_MESSAGES_BEFORE_DIRECTIONS;
+var private const int VOICE_MESSAGES_BEFORE_INSULTS;
+var private const int VOICE_MESSAGES_BEFORE_TRADER;
+var private const int VOICE_MESSAGES_BEFORE_AUTO;
+var private const int VOICE_MESSAGES_TOTAL;
-protected function Constructor()
-{
- onMessageSignal = ChatAPI_OnMessage_Signal(
- _.memory.Allocate(class'ChatAPI_OnMessage_Signal'));
- onMessageForSignal = ChatAPI_OnMessageFor_Signal(
- _.memory.Allocate(class'ChatAPI_OnMessageFor_Signal'));
+var protected ChatAPI_OnMessage_Signal onMessageSignal;
+var protected ChatAPI_OnMessageFor_Signal onMessageForSignal;
+var protected ChatAPI_OnVoiceMessage_Signal onVoiceMessageSignal;
+
+protected function Constructor() {
+ onMessageSignal = ChatAPI_OnMessage_Signal(_.memory.Allocate(class'ChatAPI_OnMessage_Signal'));
+ onMessageForSignal =
+ ChatAPI_OnMessageFor_Signal(_.memory.Allocate(class'ChatAPI_OnMessageFor_Signal'));
+ onVoiceMessageSignal =
+ ChatAPI_OnVoiceMessage_Signal(_.memory.Allocate(class'ChatAPI_OnVoiceMessage_Signal'));
}
-protected function Finalizer()
-{
+protected function Finalizer() {
_.memory.Free(onMessageSignal);
_.memory.Free(onMessageForSignal);
- onMessageSignal = none;
- onMessageForSignal = none;
+ _.memory.Free(onVoiceMessageSignal);
+ onMessageSignal = none;
+ onMessageForSignal = none;
+ onVoiceMessageSignal = none;
_server.unreal.broadcasts.OnHandleText(self).Disconnect();
_server.unreal.broadcasts.OnHandleTextFor(self).Disconnect();
connectedToBroadcastAPI = false;
}
-private final function TryConnectingBroadcastSignals()
-{
- if (connectedToBroadcastAPI) {
- return;
- }
- connectedToBroadcastAPI = true;
- _server.unreal.broadcasts.OnHandleText(self).connect = HandleText;
- _server.unreal.broadcasts.OnHandleTextFor(self).connect = HandleTextFor;
-}
-
-/**
- * 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)
-{
+/// 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.
+///
+/// # Slot description
+///
+/// bool (EPlayer sender, MutableText message, bool teamMessage)
+///
+/// ## Parameters
+///
+/// * [`sender`]: `EPlayer` that has sent the message.
+/// * [`message`]: Message that `sender` has sent.
+/// This is a mutable variable and can be modified from message will be sent.
+/// * [`teamMessage`]: Is this a team message (to be sent only to players on the same team)?
+///
+/// ## Returns
+///
+/// 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`.
+public /*signal*/ function ChatAPI_OnMessage_Slot OnMessage(AcediaObject receiver) {
TryConnectingBroadcastSignals();
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, BaseText 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)
-{
+/// 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.
+///
+/// # Slot description
+///
+/// bool (EPlayer receiver, EPlayer sender, BaseText message)
+///
+/// ## Parameters
+///
+/// * [`receiver`]: `EPlayer` that will receive the message.
+/// * [`sender`]: `EPlayer` that has sent the message.
+/// * [`message`]: Message that `sender` has sent. This is an immutable variable and cannot
+/// be changed at this point. Use `OnMessage()` signal function for that.
+///
+/// ## Returns
+///
+/// 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.
+public /*signal*/ function ChatAPI_OnMessageFor_Slot OnMessageFor(AcediaObject receiver) {
TryConnectingBroadcastSignals();
return ChatAPI_OnMessageFor_Slot(onMessageForSignal.NewSlot(receiver));
}
+/// Signal that will be emitted when a player sends a voice message.
+///
+/// # Slot description
+///
+/// bool (EPlayer sender, ChatApi.BuiltInVoiceMessage message)
+///
+/// ## Parameters
+///
+/// * [`sender`]: `EPlayer` that has sent the voice message.
+/// * [`message`]: Message that `sender` has sent.
+public /*signal*/ function ChatAPI_OnVoiceMessage_Slot OnVoiceMessage(AcediaObject receiver) {
+ if (!replacedSendVoiceMessage) {
+ _.unflect.ReplaceFunction_S(
+ "KFMod.KFPlayerController.SendVoiceMessage",
+ "AcediaCore.Unflect_ChatApi_Controller.SendVoiceMessage",
+ "`ChatApi` was required to catch voice messages");
+ replacedSendVoiceMessage = true;
+ }
+ return ChatAPI_OnVoiceMessage_Slot(onVoiceMessageSignal.NewSlot(receiver));
+}
+
+public /*internal*/ function NativeVoiceMessage _enumIntoNativeVoiceMessage(
+ BuiltInVoiceMessage voiceMessage
+) {
+ local int enumValue;
+ local NativeVoiceMessage result;
+
+ enumValue = int(voiceMessage);
+ if (enumValue < VOICE_MESSAGES_BEFORE_ACKNOWLEDGEMENTS) {
+ result.type = 'SUPPORT';
+ result.index = enumValue;
+ } else if (enumValue < VOICE_MESSAGES_BEFORE_ALERTS) {
+ result.type = 'ACK';
+ result.index = enumValue - VOICE_MESSAGES_BEFORE_ACKNOWLEDGEMENTS;
+ } else if (enumValue < VOICE_MESSAGES_BEFORE_DIRECTIONS) {
+ result.type = 'ALERT';
+ result.index = enumValue - VOICE_MESSAGES_BEFORE_ALERTS;
+ } else if (enumValue < VOICE_MESSAGES_BEFORE_INSULTS) {
+ result.type = 'DIRECTION';
+ result.index = enumValue - VOICE_MESSAGES_BEFORE_DIRECTIONS;
+ } else if (enumValue < VOICE_MESSAGES_BEFORE_TRADER) {
+ result.type = 'INSULT';
+ result.index = enumValue - VOICE_MESSAGES_BEFORE_INSULTS;
+ } else if (enumValue < VOICE_MESSAGES_BEFORE_AUTO) {
+ result.type = 'TRADER';
+ result.index = enumValue - VOICE_MESSAGES_BEFORE_TRADER;
+ if (result.index >= 5) {
+ result.index += 1;
+ }
+ } else if (enumValue < VOICE_MESSAGES_TOTAL) {
+ result.type = 'AUTO';
+ result.index = enumValue - VOICE_MESSAGES_BEFORE_AUTO;
+ }
+ return result;
+}
+
+public /*internal*/ function BuiltInVoiceMessage _nativeVoiceMessageIntoEnum(
+ NativeVoiceMessage voiceMessage
+) {
+ switch (voiceMessage.type) {
+ case 'SUPPORT':
+ if (voiceMessage.index < 4) {
+ return BuiltInVoiceMessage(voiceMessage.index);
+ }
+ break;
+ case 'ACK':
+ if (voiceMessage.index < 4) {
+ return BuiltInVoiceMessage(
+ VOICE_MESSAGES_BEFORE_ACKNOWLEDGEMENTS + voiceMessage.index);
+ }
+ break;
+ case 'ALERT':
+ if (voiceMessage.index < 6) {
+ return BuiltInVoiceMessage(VOICE_MESSAGES_BEFORE_ALERTS + voiceMessage.index);
+ }
+ break;
+ case 'DIRECTION':
+ if (voiceMessage.index < 5) {
+ return BuiltInVoiceMessage(VOICE_MESSAGES_BEFORE_DIRECTIONS + voiceMessage.index);
+ }
+ break;
+ case 'INSULT':
+ if (voiceMessage.index < 2) {
+ return BuiltInVoiceMessage(VOICE_MESSAGES_BEFORE_INSULTS + voiceMessage.index);
+ }
+ break;
+ case 'TRADER':
+ if (voiceMessage.index < 12) {
+ if (voiceMessage.index < 5) {
+ return BuiltInVoiceMessage(VOICE_MESSAGES_BEFORE_TRADER + voiceMessage.index);
+ } else if (voiceMessage.index > 5) {
+ return BuiltInVoiceMessage(
+ VOICE_MESSAGES_BEFORE_TRADER + voiceMessage.index - 1);
+ }
+ }
+ break;
+ case 'AUTO':
+ if (voiceMessage.index < 25) {
+ return BuiltInVoiceMessage(VOICE_MESSAGES_BEFORE_AUTO + voiceMessage.index);
+ }
+ break;
+ default:
+ return BIVM_Unknown;
+ }
+ return BIVM_Unknown;
+}
+
+public final /*internal*/ /*native*/ function _EmitOnVoiceMessage(
+ PlayerController sender,
+ name messageType,
+ byte messageID
+) {
+ local EPlayer wrapper;
+ local NativeVoiceMessage nativeVoiceMessage;
+ local BuiltInVoiceMessage builtInVoiceMessage;
+
+ if (sender == none) return;
+ wrapper = _.players.FromController(sender);
+ if (wrapper == none) return;
+
+ nativeVoiceMessage.type = messageType;
+ nativeVoiceMessage.index = messageID;
+ builtInVoiceMessage = _nativeVoiceMessageIntoEnum(nativeVoiceMessage);
+ if (builtInVoiceMessage != BIVM_Unknown) {
+ onVoiceMessageSignal.Emit(wrapper, builtInVoiceMessage);
+ }
+ _.memory.Free(wrapper);
+}
+
+private final function TryConnectingBroadcastSignals() {
+ if (connectedToBroadcastAPI) {
+ return;
+ }
+ connectedToBroadcastAPI = true;
+ _server.unreal.broadcasts.OnHandleText(self).connect = HandleText;
+ _server.unreal.broadcasts.OnHandleTextFor(self).connect = HandleTextFor;
+}
+
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
+ 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;
@@ -145,34 +377,39 @@ private function bool HandleText(
}
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
+ 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)
- {
+ if (receiverPlayer == none) {
_.memory.Free(senderPlayer);
return true;
}
messageAsText = __().text.FromColoredString(message);
- result = onMessageForSignal.Emit( receiverPlayer, senderPlayer,
- messageAsText);
+ result = onMessageForSignal.Emit(receiverPlayer, senderPlayer, messageAsText);
_.memory.Free(messageAsText);
_.memory.Free(senderPlayer);
_.memory.Free(receiverPlayer);
return result;
}
-defaultproperties
-{
+defaultproperties {
+ VOICE_MESSAGES_BEFORE_ACKNOWLEDGEMENTS = 4
+ VOICE_MESSAGES_BEFORE_ALERTS = 8
+ VOICE_MESSAGES_BEFORE_DIRECTIONS = 14
+ VOICE_MESSAGES_BEFORE_INSULTS = 19
+ VOICE_MESSAGES_BEFORE_TRADER = 21
+ VOICE_MESSAGES_BEFORE_AUTO = 32
+ VOICE_MESSAGES_TOTAL = 57
}
\ No newline at end of file
diff --git a/sources/Chat/Events/ChatAPI_OnVoiceMessage_Signal.uc b/sources/Chat/Events/ChatAPI_OnVoiceMessage_Signal.uc
new file mode 100644
index 0000000..d754d10
--- /dev/null
+++ b/sources/Chat/Events/ChatAPI_OnVoiceMessage_Signal.uc
@@ -0,0 +1,39 @@
+/**
+ * Author: dkanus
+ * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
+ * License: GPL
+ * Copyright 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 .
+ */
+class ChatAPI_OnVoiceMessage_Signal extends Signal
+ dependsOn(ChatApi);
+
+public final function Emit(EPlayer sender, ChatApi.BuiltInVoiceMessage message) {
+ local Slot nextSlot;
+
+ StartIterating();
+ nextSlot = GetNextSlot();
+ while (nextSlot != none) {
+ ChatAPI_OnVoiceMessage_Slot(nextSlot).connect(sender, message);
+ nextSlot = GetNextSlot();
+ }
+ CleanEmptySlots();
+}
+
+defaultproperties {
+ relatedSlotClass = class'ChatAPI_OnVoiceMessage_Slot'
+}
\ No newline at end of file
diff --git a/sources/Chat/Events/ChatAPI_OnVoiceMessage_Slot.uc b/sources/Chat/Events/ChatAPI_OnVoiceMessage_Slot.uc
new file mode 100644
index 0000000..790cec9
--- /dev/null
+++ b/sources/Chat/Events/ChatAPI_OnVoiceMessage_Slot.uc
@@ -0,0 +1,41 @@
+/**
+ * Author: dkanus
+ * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
+ * License: GPL
+ * Copyright 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 .
+ */
+class ChatAPI_OnVoiceMessage_Slot extends Slot;
+
+delegate connect(
+ EPlayer sender,
+ ChatApi.BuiltInVoiceMessage message
+) {
+ DummyCall();
+}
+
+protected function Constructor() {
+ connect = none;
+}
+
+protected function Finalizer() {
+ super.Finalizer();
+ connect = none;
+}
+
+defaultproperties {
+}
\ No newline at end of file
diff --git a/sources/Chat/Tests/TEST_VoiceMessages.uc b/sources/Chat/Tests/TEST_VoiceMessages.uc
new file mode 100644
index 0000000..45eb051
--- /dev/null
+++ b/sources/Chat/Tests/TEST_VoiceMessages.uc
@@ -0,0 +1,81 @@
+/**
+ * Set of tests for `Command` class.
+ * Copyright 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 .
+ */
+class TEST_VoiceMessages extends TestCase
+ abstract
+ dependsOn(ChatApi);
+
+private static function ChatApi.NativeVoiceMessage Make(name type, int index) {
+ local ChatApi.NativeVoiceMessage result;
+
+ result.type = type;
+ result.index = index;
+ return result;
+}
+
+private static function TestEquality(name type, int index, ChatApi.BuiltInVoiceMessage builtIn) {
+ local ChatApi.NativeVoiceMessage nativeMessage;
+
+ nativeMessage.type = type;
+ nativeMessage.index = index;
+
+ Issue("Native voice messages are incorrectly converted into `BuiltInVoiceMessage`.");
+ TEST_ExpectTrue(__().chat._nativeVoiceMessageIntoEnum(nativeMessage) == builtIn);
+
+ Issue("`BuiltInVoiceMessage`s are incorrectly converted into native voice messages.");
+ TEST_ExpectTrue(__().chat._enumIntoNativeVoiceMessage(builtIn).type == type);
+ TEST_ExpectTrue(__().chat._enumIntoNativeVoiceMessage(builtIn).index == index);
+}
+
+protected static function TESTS() {
+ Context("Testing internal conversion methods between voice messages.");
+ Test_VoiceMessageConversion();
+}
+protected static function Test_VoiceMessageConversion() {
+ local ChatApi.NativeVoiceMessage nothingMessage;
+
+ nothingMessage.type = 'TRADER';
+ nothingMessage.index = 5;
+
+ TestEquality('SUPPORT', 2, BIVM_SupportAskForMoney);
+ TestEquality('ACK', 0, BIVM_AckYes);
+ TestEquality('ALERT', 5, BIVM_AlretFollowMe);
+ TestEquality('DIRECTION', 2, BIVM_DirectionGoDownstairs);
+ TestEquality('INSULT', 1, BIVM_InsultPlayers);
+ TestEquality('TRADER', 2, BIVM_TraderShopOpen);
+ TestEquality('TRADER', 4, BIVM_Trader30SecondsUntilShopCloses);
+ TestEquality('TRADER', 6, BIVM_TraderShopClosed);
+ TestEquality('TRADER', 7, BIVM_TraderCompliment);
+ TestEquality('TRADER', 11, BIVM_TraderHurryUp2);
+ TestEquality('AUTO', 0, BIVM_AutoWelding);
+ TestEquality('AUTO', 16, BIVM_AutoSirenScream);
+ TestEquality('AUTO', 24, BIVM_AutoEquipFireaxe);
+
+ // Test unknown separately
+ Issue("Native voice messages are incorrectly converted into `BuiltInVoiceMessage`.");
+ TEST_ExpectTrue(__().chat._nativeVoiceMessageIntoEnum(nothingMessage) == BIVM_Unknown);
+
+ Issue("`BuiltInVoiceMessage`s are incorrectly converted into native voice messages.");
+ TEST_ExpectTrue(__().chat._enumIntoNativeVoiceMessage(BIVM_Unknown).type == '');
+ TEST_ExpectTrue(__().chat._enumIntoNativeVoiceMessage(BIVM_Unknown).index == 0);
+}
+defaultproperties {
+ caseName = "Voice messages"
+ caseGroup = "ChatAPI"
+}
\ No newline at end of file
diff --git a/sources/Chat/Unflect/Unflect_ChatApi_Controller.uc b/sources/Chat/Unflect/Unflect_ChatApi_Controller.uc
new file mode 100644
index 0000000..4051748
--- /dev/null
+++ b/sources/Chat/Unflect/Unflect_ChatApi_Controller.uc
@@ -0,0 +1,59 @@
+/**
+ * Author: dkanus
+ * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
+ * License: GPL
+ * Copyright 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 .
+ */
+class Unflect_ChatApi_Controller extends KFPlayerController
+ dependsOn(ChatAPI);
+
+function SendVoiceMessage(
+ PlayerReplicationInfo sender,
+ PlayerReplicationInfo statedRecipient,
+ name messageType,
+ byte messageID,
+ name broadcastType,
+ optional Pawn soundSender,
+ optional vector senderLocation
+) {
+ local Controller recepient;
+ local KFPlayerController kfPlayerRecepient;
+
+ // Don't allow dead people to talk
+ if (pawn == none) return;
+ if (!AllowVoiceMessage(messageType)) return;
+
+ class'Global'.static.GetInstance().chat._EmitOnVoiceMessage(self, messageType, messageID);
+ recepient = level.controllerList;
+ while (recepient != none) {
+ kfPlayerRecepient = KFPlayerController(recepient);
+ if (kfPlayerRecepient != none) {
+ kfPlayerRecepient.ClientLocationalVoiceMessage(
+ sender,
+ statedRecipient,
+ messagetype,
+ messageID,
+ soundSender,
+ senderLocation);
+ }
+ recepient = recepient.nextController;
+ }
+}
+
+defaultproperties {
+}
\ No newline at end of file
diff --git a/sources/Manifest.uc b/sources/Manifest.uc
index ce9bd9c..760968f 100644
--- a/sources/Manifest.uc
+++ b/sources/Manifest.uc
@@ -60,4 +60,5 @@ defaultproperties
testCases(31) = class'TEST_UTF8EncoderDecoder'
testCases(32) = class'TEST_AvariceStreamReader'
testCases(33) = class'TEST_Unflect'
+ testCases(34) = class'TEST_VoiceMessages'
}
\ No newline at end of file
diff --git a/sources/Players/EPlayer.uc b/sources/Players/EPlayer.uc
index 20dd00e..8eed122 100644
--- a/sources/Players/EPlayer.uc
+++ b/sources/Players/EPlayer.uc
@@ -17,7 +17,8 @@
* You should have received a copy of the GNU General Public License
* along with Acedia. If not, see .
*/
-class EPlayer extends EInterface;
+class EPlayer extends EInterface
+ dependsOn(ChatApi);
// How this `EPlayer` is identified by the server
var private User identity;
@@ -504,6 +505,19 @@ public final function /* borrow */ ConsoleWriter BorrowConsole()
return consoleInstance.ForPlayer(self);
}
+/// Sends specified voice message from the caller player to all players in the game.
+public final function SendVoiceMessage(ChatApi.BuiltInVoiceMessage voiceMessage) {
+ local PlayerController myController;
+ local ChatApi.NativeVoiceMessage nativeMessage;
+
+ if (voiceMessage == BIVM_Unknown) return;
+ myController = GetController();
+ if (myController == none) return;
+ nativeMessage = _.chat._enumIntoNativeVoiceMessage(voiceMessage);
+
+ myController.ServerSpeech(nativeMessage.type, nativeMessage.index, "");
+}
+
/// Sends a text message to notify the player about a particular event or situation, displayed
/// as a notification.
///