Compare commits

..

6 Commits

  1. 3
      sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc
  2. 2
      sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc
  3. 1
      sources/BaseAPI/API/Unflect/UnflectApi.uc
  4. 439
      sources/Chat/ChatAPI.uc
  5. 39
      sources/Chat/Events/ChatAPI_OnVoiceMessage_Signal.uc
  6. 41
      sources/Chat/Events/ChatAPI_OnVoiceMessage_Slot.uc
  7. 81
      sources/Chat/Tests/TEST_VoiceMessages.uc
  8. 59
      sources/Chat/Unflect/Unflect_ChatApi_Controller.uc
  9. 1
      sources/Manifest.uc
  10. 16
      sources/Players/EPlayer.uc

3
sources/BaseAPI/API/Commands/BuiltInCommands/ACommandNotify.uc

@ -19,7 +19,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class ACommandNotify extends Command; class ACommandNotify extends Command
dependsOn(ChatApi);
protected function BuildData(CommandDataBuilder builder) { protected function BuildData(CommandDataBuilder builder) {
builder.Name(P("notify")); builder.Name(P("notify"));

2
sources/BaseAPI/API/Unflect/Tests/TEST_Unflect.uc

@ -1,6 +1,6 @@
/** /**
* Set of tests for `Command` class. * Set of tests for `Command` class.
* Copyright 2021 - 2022 Anton Tarasenko * Copyright 2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *

1
sources/BaseAPI/API/Unflect/UnflectApi.uc

@ -306,6 +306,7 @@ private final function bool _replaceFunction(Text oldFunctionLowerCase, Text new
_.memory.Free(initialCode); _.memory.Free(initialCode);
} }
replace.script = with.script; replace.script = with.script;
Log("TRULLY!" @ replace @ with);
return true; return true;
} }

439
sources/Chat/ChatAPI.uc

@ -1,6 +1,8 @@
/** /**
* API that provides functions for working with chat. * Author: dkanus
* Copyright 2022 Anton Tarasenko * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
* License: GPL
* Copyright 2022-2023 Anton Tarasenko
*------------------------------------------------------------------------------ *------------------------------------------------------------------------------
* This file is part of Acedia. * This file is part of Acedia.
* *
@ -17,112 +19,342 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
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; 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; /// Auxiliary constants that store amount of values in [`BuiltInVoiceMessage`] before
var protected ChatAPI_OnMessageFor_Signal onMessageForSignal; /// 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() var protected ChatAPI_OnMessage_Signal onMessageSignal;
{ var protected ChatAPI_OnMessageFor_Signal onMessageForSignal;
onMessageSignal = ChatAPI_OnMessage_Signal( var protected ChatAPI_OnVoiceMessage_Signal onVoiceMessageSignal;
_.memory.Allocate(class'ChatAPI_OnMessage_Signal'));
onMessageForSignal = ChatAPI_OnMessageFor_Signal( protected function Constructor() {
_.memory.Allocate(class'ChatAPI_OnMessageFor_Signal')); 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(onMessageSignal);
_.memory.Free(onMessageForSignal); _.memory.Free(onMessageForSignal);
onMessageSignal = none; _.memory.Free(onVoiceMessageSignal);
onMessageForSignal = none; onMessageSignal = none;
onMessageForSignal = none;
onVoiceMessageSignal = none;
_server.unreal.broadcasts.OnHandleText(self).Disconnect(); _server.unreal.broadcasts.OnHandleText(self).Disconnect();
_server.unreal.broadcasts.OnHandleTextFor(self).Disconnect(); _server.unreal.broadcasts.OnHandleTextFor(self).Disconnect();
connectedToBroadcastAPI = false; connectedToBroadcastAPI = false;
} }
private final function TryConnectingBroadcastSignals() /// Signal that will be emitted when a player sends a message into the chat.
{ ///
if (connectedToBroadcastAPI) { /// Allows to modify message before sending it, as well as prevent it from being sent at all.
return; ///
} /// Return `false` to prevent message from being sent.
connectedToBroadcastAPI = true; /// If `false` is returned, signal propagation to the remaining handlers will also be interrupted.
_server.unreal.broadcasts.OnHandleText(self).connect = HandleText; ///
_server.unreal.broadcasts.OnHandleTextFor(self).connect = HandleTextFor; /// # Slot description
} ///
/// bool <slot>(EPlayer sender, MutableText message, bool teamMessage)
/** ///
* Signal that will be emitted when a player sends a message into the chat. /// ## Parameters
* Allows to modify message before sending it, as well as prevent it from ///
* being sent at all. /// * [`sender`]: `EPlayer` that has sent the message.
* /// * [`message`]: Message that `sender` has sent.
* Return `false` to prevent message from being sent. /// This is a mutable variable and can be modified from message will be sent.
* If `false` is returned, signal propagation to the remaining handlers will /// * [`teamMessage`]: Is this a team message (to be sent only to players on the same team)?
* also be interrupted. ///
* /// ## Returns
* [Signature] ///
* bool <slot>(EPlayer sender, MutableText message, bool teamMessage) /// 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`.
* @param sender `EPlayer` that has sent the message. public /*signal*/ function ChatAPI_OnMessage_Slot OnMessage(AcediaObject receiver) {
* @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)
{
TryConnectingBroadcastSignals(); TryConnectingBroadcastSignals();
return ChatAPI_OnMessage_Slot(onMessageSignal.NewSlot(receiver)); return ChatAPI_OnMessage_Slot(onMessageSignal.NewSlot(receiver));
} }
/** /// Signal that will be emitted when a player sends a message into the chat.
* 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 /// Allows to modify message before sending it, as well as prevent it from being sent at all.
* being sent at all. ///
* /// Return `false` to prevent message from being sent to a specific player.
* 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.
* If `false` is returned, signal propagation to the remaining handlers will ///
* also be interrupted. /// # Slot description
* ///
* [Signature] /// bool <slot>(EPlayer receiver, EPlayer sender, BaseText message)
* bool <slot>(EPlayer receiver, EPlayer sender, BaseText message) ///
* /// ## Parameters
* @param receiver `EPlayer` that will receive the message. ///
* @param sender `EPlayer` that has sent the message. /// * [`receiver`]: `EPlayer` that will receive the message.
* @param message Message that `sender` has sent. This is an immutable /// * [`sender`]: `EPlayer` that has sent the message.
* variable and cannot be changed at this point. Use `OnMessage()` /// * [`message`]: Message that `sender` has sent. This is an immutable variable and cannot
* signal function for that. /// 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 /// ## Returns
* all handlers will return `true`. ///
* However decision whether to send message or not is made for /// Return `false` to prevent this message from being sent to a particular player and
* every player separately. /// `true` otherwise.
*/ /// Message will be sent only if all handlers will return `true`.
/* SIGNAL */ /// However decision whether to send message or not is made for every player separately.
public function ChatAPI_OnMessageFor_Slot OnMessageFor( public /*signal*/ function ChatAPI_OnMessageFor_Slot OnMessageFor(AcediaObject receiver) {
AcediaObject receiver)
{
TryConnectingBroadcastSignals(); TryConnectingBroadcastSignals();
return ChatAPI_OnMessageFor_Slot(onMessageForSignal.NewSlot(receiver)); return ChatAPI_OnMessageFor_Slot(onMessageForSignal.NewSlot(receiver));
} }
/// Signal that will be emitted when a player sends a voice message.
///
/// # Slot description
///
/// bool <slot>(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( private function bool HandleText(
Actor sender, Actor sender,
out string message, out string message,
name messageType, name messageType,
bool teamMessage) bool teamMessage
{ ) {
local bool result; local bool result;
local MutableText messageAsText; local MutableText messageAsText;
local EPlayer senderPlayer; local EPlayer senderPlayer;
// We only want to catch chat messages from a player
// We only want to catch chat messages from a player
if (messageType != 'Say' && messageType != 'TeamSay') return true; if (messageType != 'Say' && messageType != 'TeamSay') return true;
senderPlayer = _.players.FromController(PlayerController(sender)); senderPlayer = _.players.FromController(PlayerController(sender));
if (senderPlayer == none) return true; if (senderPlayer == none) return true;
@ -145,34 +377,39 @@ private function bool HandleText(
} }
private function bool HandleTextFor( private function bool HandleTextFor(
PlayerController receiver, PlayerController receiver,
Actor sender, Actor sender,
out string message, out string message,
name messageType) name messageType
{ ) {
local bool result; local bool result;
local Text messageAsText; local Text messageAsText;
local EPlayer senderPlayer, receiverPlayer; local EPlayer senderPlayer, receiverPlayer;
// We only want to catch chat messages from another player
// We only want to catch chat messages from another player
if (messageType != 'Say' && messageType != 'TeamSay') return true; if (messageType != 'Say' && messageType != 'TeamSay') return true;
senderPlayer = _.players.FromController(PlayerController(sender)); senderPlayer = _.players.FromController(PlayerController(sender));
if (senderPlayer == none) return true; if (senderPlayer == none) return true;
receiverPlayer = _.players.FromController(receiver); receiverPlayer = _.players.FromController(receiver);
if (receiverPlayer == none) if (receiverPlayer == none) {
{
_.memory.Free(senderPlayer); _.memory.Free(senderPlayer);
return true; return true;
} }
messageAsText = __().text.FromColoredString(message); messageAsText = __().text.FromColoredString(message);
result = onMessageForSignal.Emit( receiverPlayer, senderPlayer, result = onMessageForSignal.Emit(receiverPlayer, senderPlayer, messageAsText);
messageAsText);
_.memory.Free(messageAsText); _.memory.Free(messageAsText);
_.memory.Free(senderPlayer); _.memory.Free(senderPlayer);
_.memory.Free(receiverPlayer); _.memory.Free(receiverPlayer);
return result; 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
} }

39
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 <https://www.gnu.org/licenses/>.
*/
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'
}

41
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 <https://www.gnu.org/licenses/>.
*/
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 {
}

81
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 <https://www.gnu.org/licenses/>.
*/
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"
}

59
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 <https://www.gnu.org/licenses/>.
*/
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 {
}

1
sources/Manifest.uc

@ -60,4 +60,5 @@ defaultproperties
testCases(31) = class'TEST_UTF8EncoderDecoder' testCases(31) = class'TEST_UTF8EncoderDecoder'
testCases(32) = class'TEST_AvariceStreamReader' testCases(32) = class'TEST_AvariceStreamReader'
testCases(33) = class'TEST_Unflect' testCases(33) = class'TEST_Unflect'
testCases(34) = class'TEST_VoiceMessages'
} }

16
sources/Players/EPlayer.uc

@ -17,7 +17,8 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. * along with Acedia. If not, see <https://www.gnu.org/licenses/>.
*/ */
class EPlayer extends EInterface; class EPlayer extends EInterface
dependsOn(ChatApi);
// How this `EPlayer` is identified by the server // How this `EPlayer` is identified by the server
var private User identity; var private User identity;
@ -504,6 +505,19 @@ public final function /* borrow */ ConsoleWriter BorrowConsole()
return consoleInstance.ForPlayer(self); 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 /// Sends a text message to notify the player about a particular event or situation, displayed
/// as a notification. /// as a notification.
/// ///

Loading…
Cancel
Save