From 5f61082dfededdeb43561ca3df75e0bd12671fd8 Mon Sep 17 00:00:00 2001 From: PabstMirror Date: Tue, 18 May 2021 13:19:01 -0500 Subject: [PATCH 1/3] TS - Capture multichannel microphone data as mono --- extensions/src/ACRE2Core/SoundEngine.cpp | 2 +- extensions/src/ACRE2Core/SoundMonoChannel.cpp | 13 ++++++++++--- extensions/src/ACRE2Core/SoundMonoChannel.h | 2 +- extensions/src/ACRE2Core/SoundPlayback.cpp | 2 +- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/extensions/src/ACRE2Core/SoundEngine.cpp b/extensions/src/ACRE2Core/SoundEngine.cpp index e23fb5a10..d075714cd 100644 --- a/extensions/src/ACRE2Core/SoundEngine.cpp +++ b/extensions/src/ACRE2Core/SoundEngine.cpp @@ -37,7 +37,7 @@ acre::Result CSoundEngine::onEditPlaybackVoiceDataEvent(acre::id_t id, short* sa for (size_t i = 0; i < player->channels.size(); ++i) { if (player->channels[i]) { player->channels[i]->lock(); - player->channels[i]->In(samples, sampleCount); + player->channels[i]->In(samples, sampleCount, channels); player->channels[i]->unlock(); } } diff --git a/extensions/src/ACRE2Core/SoundMonoChannel.cpp b/extensions/src/ACRE2Core/SoundMonoChannel.cpp index 29600d480..756f86ca2 100644 --- a/extensions/src/ACRE2Core/SoundMonoChannel.cpp +++ b/extensions/src/ACRE2Core/SoundMonoChannel.cpp @@ -44,10 +44,17 @@ CSoundChannelMono::~CSoundChannelMono() { } } -int CSoundChannelMono::In(short *samples, int sampleCount) { +int CSoundChannelMono::In(short *samples, int sampleCount, const int channels_) { //memset(samples, 0x00, sampleCount*sizeof(short)); if (this->bufferLength+sampleCount <= this->bufferMaxSize) { - memcpy(this->buffer+this->bufferLength, samples, sampleCount*sizeof(short)); + if (channels_ == 1) { + memcpy(this->buffer + this->bufferLength, samples, sampleCount * sizeof(short)); + } else { + // rare but for multi channel input just capture mono, samples[channels*sampleCount]={Left,Right,Left,...} + for (int i = 0; i < sampleCount; i++) { + this->buffer[this->bufferLength + i] = samples[channels_*i]; + } + } this->bufferLength += sampleCount; } return this->bufferLength; @@ -113,4 +120,4 @@ CSoundMixdownEffect * CSoundChannelMono::getMixdownEffectInsert(int index) { if (index > 7) return NULL; return this->mixdownEffects[index]; -} \ No newline at end of file +} diff --git a/extensions/src/ACRE2Core/SoundMonoChannel.h b/extensions/src/ACRE2Core/SoundMonoChannel.h index 907197874..f48a81b30 100644 --- a/extensions/src/ACRE2Core/SoundMonoChannel.h +++ b/extensions/src/ACRE2Core/SoundMonoChannel.h @@ -27,7 +27,7 @@ class CSoundChannelMono : public CLockable { CSoundChannelMono( int length, bool singleShot ); ~CSoundChannelMono(); - int In(short *samples, int sampleCount); + int In(short *samples, int sampleCount, const int channels_); int Out(short *samples, int sampleCount); int GetCurrentBufferSize() { return this->bufferLength-this->bufferPos; }; bool IsOneShot() { return this->oneShot; }; diff --git a/extensions/src/ACRE2Core/SoundPlayback.cpp b/extensions/src/ACRE2Core/SoundPlayback.cpp index 9ddd09085..95fece2a6 100644 --- a/extensions/src/ACRE2Core/SoundPlayback.cpp +++ b/extensions/src/ACRE2Core/SoundPlayback.cpp @@ -78,7 +78,7 @@ acre::Result CSoundPlayback::playSound(std::string id, acre::vec3_fp32_t positio tempChannel->getMixdownEffectInsert(0)->setParam("isWorld", 0x00000000); } - tempChannel->In((short *)waveFile.GetData(), waveFile.GetSize()/sizeof(short)); + tempChannel->In((short *)waveFile.GetData(), waveFile.GetSize()/sizeof(short), 1); CEngine::getInstance()->getSoundEngine()->getSoundMixer()->unlock(); return acre::Result::ok; } From 8b686cd072caa8829e22c916033e9c9cb369cf7a Mon Sep 17 00:00:00 2001 From: PabstMirror Date: Wed, 19 May 2021 01:35:42 -0500 Subject: [PATCH 2/3] Add sys_headless --- addons/sys_headless/$PBOPREFIX$ | 1 + addons/sys_headless/CfgEventHandlers.hpp | 17 ++++ addons/sys_headless/XEH_PREP.hpp | 4 + addons/sys_headless/XEH_postInit.sqf | 19 ++++ addons/sys_headless/XEH_preInit.sqf | 9 ++ addons/sys_headless/XEH_preStart.sqf | 3 + addons/sys_headless/config.cpp | 16 ++++ addons/sys_headless/fnc_addRadio.sqf | 58 +++++++++++ .../sys_headless/fnc_handleGetHeadlessID.sqf | 30 ++++++ addons/sys_headless/fnc_start.sqf | 96 +++++++++++++++++++ addons/sys_headless/fnc_stop.sqf | 25 +++++ addons/sys_headless/script_component.hpp | 9 ++ extensions/src/ACRE2Core/Engine.cpp | 2 + extensions/src/ACRE2Core/getHeadlessID.h | 29 ++++++ extensions/src/ACRE2Shared/IClient.h | 2 + extensions/src/ACRE2TS/TS3Client.cpp | 26 +++++ extensions/src/ACRE2TS/TS3Client.h | 9 ++ 17 files changed, 355 insertions(+) create mode 100644 addons/sys_headless/$PBOPREFIX$ create mode 100644 addons/sys_headless/CfgEventHandlers.hpp create mode 100644 addons/sys_headless/XEH_PREP.hpp create mode 100644 addons/sys_headless/XEH_postInit.sqf create mode 100644 addons/sys_headless/XEH_preInit.sqf create mode 100644 addons/sys_headless/XEH_preStart.sqf create mode 100644 addons/sys_headless/config.cpp create mode 100644 addons/sys_headless/fnc_addRadio.sqf create mode 100644 addons/sys_headless/fnc_handleGetHeadlessID.sqf create mode 100644 addons/sys_headless/fnc_start.sqf create mode 100644 addons/sys_headless/fnc_stop.sqf create mode 100644 addons/sys_headless/script_component.hpp create mode 100644 extensions/src/ACRE2Core/getHeadlessID.h diff --git a/addons/sys_headless/$PBOPREFIX$ b/addons/sys_headless/$PBOPREFIX$ new file mode 100644 index 000000000..e65a24e0c --- /dev/null +++ b/addons/sys_headless/$PBOPREFIX$ @@ -0,0 +1 @@ +idi\acre\addons\sys_headless diff --git a/addons/sys_headless/CfgEventHandlers.hpp b/addons/sys_headless/CfgEventHandlers.hpp new file mode 100644 index 000000000..0d3301d6e --- /dev/null +++ b/addons/sys_headless/CfgEventHandlers.hpp @@ -0,0 +1,17 @@ +class Extended_PreStart_EventHandlers { + class ADDON { + init = QUOTE(call COMPILE_FILE(XEH_preStart)); + }; +}; + +class Extended_PreInit_EventHandlers { + class ADDON { + init = QUOTE(call COMPILE_FILE(XEH_preInit)); + }; +}; + +class Extended_PostInit_EventHandlers { + class ADDON { + init = QUOTE(call COMPILE_FILE(XEH_postInit)); + }; +}; diff --git a/addons/sys_headless/XEH_PREP.hpp b/addons/sys_headless/XEH_PREP.hpp new file mode 100644 index 000000000..a6251df70 --- /dev/null +++ b/addons/sys_headless/XEH_PREP.hpp @@ -0,0 +1,4 @@ +PREP(addRadio); +PREP(handleGetHeadlessID); +PREP(start); +PREP(stop); diff --git a/addons/sys_headless/XEH_postInit.sqf b/addons/sys_headless/XEH_postInit.sqf new file mode 100644 index 000000000..7e1117838 --- /dev/null +++ b/addons/sys_headless/XEH_postInit.sqf @@ -0,0 +1,19 @@ +#include "script_component.hpp" + +["handleGetHeadlessID", { call FUNC(handleGetHeadlessID) }] call EFUNC(sys_rpc,addProcedure); +[QEGVAR(sys_radio,returnRadioId), { call FUNC(handleReturnRadioId) }] call CBA_fnc_addEventHandler; + +if (hasInterface) then { + [ + {!isNull findDisplay 46}, + { + (findDisplay 46) displayAddEventHandler ["Unload", { + // Cleanup all headless ID's at mission end (or they will keep playing next mission, lol) + { + TRACE_1("RPC STOP: mission end",_x); + ["ext_remoteStopSpeaking", format ["%1,", _x]] call EFUNC(sys_rpc,callRemoteProcedure); + } forEach (missionNamespace getVariable [QGVAR(idsToCleanup), []]) + }]; + } + ] call CBA_fnc_waitUntilAndExecute; +}; diff --git a/addons/sys_headless/XEH_preInit.sqf b/addons/sys_headless/XEH_preInit.sqf new file mode 100644 index 000000000..b47cf6628 --- /dev/null +++ b/addons/sys_headless/XEH_preInit.sqf @@ -0,0 +1,9 @@ +#include "script_component.hpp" + +ADDON = false; + +PREP_RECOMPILE_START; +#include "XEH_PREP.hpp" +PREP_RECOMPILE_END; + +ADDON = true; diff --git a/addons/sys_headless/XEH_preStart.sqf b/addons/sys_headless/XEH_preStart.sqf new file mode 100644 index 000000000..022888575 --- /dev/null +++ b/addons/sys_headless/XEH_preStart.sqf @@ -0,0 +1,3 @@ +#include "script_component.hpp" + +#include "XEH_PREP.hpp" diff --git a/addons/sys_headless/config.cpp b/addons/sys_headless/config.cpp new file mode 100644 index 000000000..dcd3e5018 --- /dev/null +++ b/addons/sys_headless/config.cpp @@ -0,0 +1,16 @@ +#include "script_component.hpp" + +class CfgPatches { + class ADDON { + name = COMPONENT_NAME; + units[] = {}; + weapons[] = {}; + requiredVersion = REQUIRED_VERSION; + requiredAddons[] = {"acre_sys_core"}; + author = ECSTRING(main,Author); + url = ECSTRING(main,URL); + VERSION_CONFIG; + }; +}; + +#include "CfgEventHandlers.hpp" diff --git a/addons/sys_headless/fnc_addRadio.sqf b/addons/sys_headless/fnc_addRadio.sqf new file mode 100644 index 000000000..a9c19c37e --- /dev/null +++ b/addons/sys_headless/fnc_addRadio.sqf @@ -0,0 +1,58 @@ +#include "script_component.hpp" +/* + * Author: ACRE2Team + * Adds a unique radio to a AI unit + * + * Arguments: + * 0: Unit + * 1: Radio Base class + * 2: Preset (optional) + * + * Return Value: + * Added + * + * Example: + * [q2, "acre_prc343"] call acre_sys_headless_fnc_add + * + * Public: No + */ +[{ + params ["_unit", "_radioBaseClass", ["_preset", "", [""]]]; + TRACE_3("addRadio",_unit,_radioBaseClass,_preset); + + if (!isServer) exitWith { ERROR_1("only use on server - %1",_this); false }; + if (!alive _unit) exitWith { ERROR_1("bad unit - %1",_this); false }; + if (!([_radioBaseClass] call EFUNC(api,isBaseRadio))) exitWith { ERROR_1("bad radio - %1",_this); false }; + if (!([_unit, _radioBaseClass] call CBA_fnc_canAddItem)) exitWith { ERROR_1("cannot add radio - %1",_this); false }; + + GVAR(newUniqueRadio) = ""; // Because this is all running on the server, this gvar will be set by the EH in-line + + private _tempEH = [QEGVAR(sys_radio,returnRadioId), { + params ["_unit", "_class"]; + TRACE_2("returnRadioId tempEH",_unit,_class); + GVAR(newUniqueRadio) = _class; + }] call CBA_fnc_addEventHandler; + + ["acre_getRadioId", [_unit, _radioBaseClass, QEGVAR(sys_radio,returnRadioId)]] call CBA_fnc_serverEvent; + + TRACE_2("after getRadioID",_tempEH,GVAR(newUniqueRadio)); + [QEGVAR(sys_radio,returnRadioId), _tempEH] call CBA_fnc_removeEventHandler; + + if (GVAR(newUniqueRadio) == "") exitWith { + ERROR_1("failed to get radio ID - %1",_this); + false + }; + + _unit addItem GVAR(newUniqueRadio); + + // initialize the new radio + if (_preset == "") then { + _preset = [_radioBaseClass] call EFUNC(sys_data,getRadioPresetName); + TRACE_2("got default preset",_radioBaseClass,_preset); + }; + [GVAR(newUniqueRadio), _preset] call EFUNC(sys_radio,initDefaultRadio); + + ["acre_acknowledgeId", [GVAR(newUniqueRadio), _unit]] call CBA_fnc_serverEvent; + + true +}, _this] call CBA_fnc_directCall; diff --git a/addons/sys_headless/fnc_handleGetHeadlessID.sqf b/addons/sys_headless/fnc_handleGetHeadlessID.sqf new file mode 100644 index 000000000..cf5744ccb --- /dev/null +++ b/addons/sys_headless/fnc_handleGetHeadlessID.sqf @@ -0,0 +1,30 @@ +#include "script_component.hpp" +/* + * Author: ACRE2Team + * Get the alive player units matching the current channel. If global chat, all units are targeted. + * + * Arguments: + * None + * + * Return Value: + * Handled + * + * Example: + * ["1:2", "Botty", 499] call acre_sys_headless_fnc_handleGetHeadlessID + * + * Public: No + */ + +params [["_netId","",[""]],["_targetName","",[""]],["_targetID","0",[""]]]; +TRACE_3("handleGetHeadlessID",_netId,_targetName,_targetID); + +private _unit = objectFromNetId _netId; +if (isNull _unit) exitWith { + WARNING_1("null unit %1",_this); +}; +_targetID = parseNumber _targetID; +if (_targetID == 0) then { + WARNING_2("Cannot find TSID - Headless Unit [%1] DisplayName [%2]",_unit,_tsDisplayName); +}; + +_unit setVariable [QGVAR(virtualID), _targetID]; diff --git a/addons/sys_headless/fnc_start.sqf b/addons/sys_headless/fnc_start.sqf new file mode 100644 index 000000000..e847a1723 --- /dev/null +++ b/addons/sys_headless/fnc_start.sqf @@ -0,0 +1,96 @@ +#include "script_component.hpp" +/* + * Author: ACRE2Team + * Starts a unit speaking from a headless TS client + * Must be called on all clients + * + * Arguments: + * 0: Unit + * 1: TS Display Name (name of a TS source inside the ACRE channel) + * 2: Language Index + * 3: Speaking Type (0 direct, 1 radio,...) + * 4: Radio ID + * + * Return Value: + * Started + * + * Example: + * [q2, "Botty", 0, 0, ""] call acre_sys_headless_fnc_start + * [q2, "Botty", 0, 1, "ACRE_PRC343_ID_2"] call acre_sys_headless_fnc_start + * + * Public: No + */ + +if (!hasInterface) exitWith {}; + +params ["_unit", "_tsDisplayName", "_languageId", "_speakingType", "_radioId"]; +TRACE_4("start",_unit,_tsName,_languageId,_speakingType,_radioId); + +if (!alive _unit) exitWith { ERROR_1("bad unit",_this); false }; +if (!isNil {_unit getVariable [QGVAR(keepRunning), nil]}) exitWith { ERROR_1("unit is already active",_this); false }; + +if (isNil QGVAR(idsToCleanup)) then { GVAR(idsToCleanup) = []; }; + +private _netId = netId _unit; + +// start getting tsId, won't have result until next frame+ +private _lastKnownId = 0; +_unit setVariable [QGVAR(virtualID), _lastKnownId]; +["getHeadlessID", [_netId, _tsDisplayName]] call EFUNC(sys_rpc,callRemoteProcedure); + +_unit setVariable [QGVAR(keepRunning), true]; + +[{ + params ["_args", "_pfid"]; + _args params ["_unit", "_tsDisplayName", "_lastKnownId", "_languageId", "_netId", "_speakingType", "_radioID"]; + private _currentId = _unit getVariable [QGVAR(virtualID), 0]; + private _isSpeaking = [_unit] call EFUNC(api,isSpeaking); + private _keepRunning = (_unit getVariable [QGVAR(keepRunning), false]) && {alive _unit}; + TRACE_5("tick",_unit,_lastKnownId,_currentId,_isSpeaking,_keepRunning); + + if ((_lastKnownId == 0) && {_currentId != 0}) then { + // When we first get a new valid ID, Ensure bot is not currently in plugin's speakingList (just in case it was never cleared) + TRACE_1("RPC STOP: reset on new ID",_currentId); + [ + "ext_remoteStopSpeaking", + format ["%1,", _currentId] + ] call EFUNC(sys_rpc,callRemoteProcedure); + }; + if (_isSpeaking) then { + // Handle client TS closed / pipe error - Need to manually do sqf-stopspeaking to remove from speakers list + if (!EGVAR(sys_io,serverStarted)) then { + TRACE_2("manual sqf stopspeaking: plugin problems",_currentId,_keepRunning); + _currentId = 0; + [str _lastKnownId, _netId] call EFUNC(sys_core,remoteStopSpeaking); + }; + // Normal shutdown or we lost tsID (bot disconnect) + if ((!_keepRunning) || {_currentId == 0}) then { + TRACE_2("RPC STOP: shutdown or bad ID",_keepRunning,_currentId); + [ + "ext_remoteStopSpeaking", + format ["%1,", _lastKnownId] + ] call EFUNC(sys_rpc,callRemoteProcedure); + }; + } else { + if (_keepRunning && {_currentId != 0}) then { + TRACE_1("RPC START: normal",_currentId); + [ + "ext_remoteStartSpeaking", + format ["%1,%2,%3,%4,%5,%6,", _currentId, _languageId, _netId, _speakingType, _radioId, 1] + ] call EFUNC(sys_rpc,callRemoteProcedure); + }; + }; + _args set [2, _currentId]; + if (_currentId !=0) then { GVAR(idsToCleanup) pushBackUnique _currentId; }; + + if (_keepRunning) then { + // Keep checking ID, this handles a bot disconnecting and reconnecting under a different ID + ["getHeadlessID", [_netId, _tsDisplayName]] call EFUNC(sys_rpc,callRemoteProcedure); + } else { + TRACE_1("pfeh stop",_this); + [_pfid] call CBA_fnc_removePerFrameHandler; + _unit setVariable [QGVAR(keepRunning), nil]; + }; +}, 0.5, [_unit, _tsDisplayName, _lastKnownId, _languageId, _netID, _speakingType, _radioId]] call CBA_fnc_addPerFrameHandler; + +true diff --git a/addons/sys_headless/fnc_stop.sqf b/addons/sys_headless/fnc_stop.sqf new file mode 100644 index 000000000..54a2e0e72 --- /dev/null +++ b/addons/sys_headless/fnc_stop.sqf @@ -0,0 +1,25 @@ +#include "script_component.hpp" +/* + * Author: ACRE2Team + * Stops a unit speaking from a headless TS client + * + * Arguments: + * 0: Unit + * + * Return Value: + * None + * + * Example: + * [q2] call acre_sys_headless_fnc_stop + * + * Public: No + */ +if (!hasInterface) exitWith {}; + +params ["_unit"]; +TRACE_1("stop"_unit); + +if (isNil {_unit getVariable [QGVAR(keepRunning), nil]}) exitWith { ERROR_1("unit is not active",_this); }; + +// Won't have immediete effects, will shutdown at next PFEH interval +_unit setVariable [QGVAR(keepRunning), false]; diff --git a/addons/sys_headless/script_component.hpp b/addons/sys_headless/script_component.hpp new file mode 100644 index 000000000..6614af297 --- /dev/null +++ b/addons/sys_headless/script_component.hpp @@ -0,0 +1,9 @@ +#define COMPONENT sys_headless +#define COMPONENT_BEAUTIFIED Headless +#include "\idi\acre\addons\main\script_mod.hpp" + +// #define DEBUG_MODE_FULL +// #define DISABLE_COMPILE_CACHE +// #define ENABLE_PERFORMANCE_COUNTERS + +#include "\idi\acre\addons\main\script_macros.hpp" diff --git a/extensions/src/ACRE2Core/Engine.cpp b/extensions/src/ACRE2Core/Engine.cpp index 3e9a6c5ff..0a0db7426 100644 --- a/extensions/src/ACRE2Core/Engine.cpp +++ b/extensions/src/ACRE2Core/Engine.cpp @@ -29,6 +29,7 @@ #include "setSelectableVoiceCurve.h" #include "setSetting.h" #include "setTs3ChannelDetails.h" +#include "getHeadlessID.h" acre::Result CEngine::initialize(IClient *client, IServer *externalServer, std::string fromPipeName, std::string toPipeName) { @@ -79,6 +80,7 @@ acre::Result CEngine::initialize(IClient *client, IServer *externalServer, std:: this->getRpcEngine()->addProcedure(new setSelectableVoiceCurve()); this->getRpcEngine()->addProcedure(new setSetting()); this->getRpcEngine()->addProcedure(new setTs3ChannelDetails()); + this->getRpcEngine()->addProcedure(new getHeadlessID()); // Initialize the client, because it never was derp this->getClient()->initialize(); diff --git a/extensions/src/ACRE2Core/getHeadlessID.h b/extensions/src/ACRE2Core/getHeadlessID.h new file mode 100644 index 000000000..952c32203 --- /dev/null +++ b/extensions/src/ACRE2Core/getHeadlessID.h @@ -0,0 +1,29 @@ +#pragma once +#include "Engine.h" +#include "IRpcFunction.h" +#include "IServer.h" +#include "Log.h" +#include "TextMessage.h" + +RPC_FUNCTION(getHeadlessID) { + const std::string netId{reinterpret_cast(vMessage->getParameter(0))}; + const std::string targetName{reinterpret_cast(vMessage->getParameter(1))}; + + // try to get ID from the displayName by searching the clientList (0 indicates not found) + const acre::id_t targetID = CEngine::getInstance()->getClient()->getClientIDByName(targetName); + + vServer->sendMessage(CTextMessage::formatNewMessage("handleGetHeadlessID", "%s,%s,%d,", netId.c_str(), targetName.c_str(), targetID)); + return acre::Result::ok; +} + +public: +inline void setName(const char *const value) final { + m_Name = value; +} +inline const char *getName() const final { + return m_Name; +} + +protected: +const char *m_Name; +}; diff --git a/extensions/src/ACRE2Shared/IClient.h b/extensions/src/ACRE2Shared/IClient.h index ceecf2418..72b29540b 100644 --- a/extensions/src/ACRE2Shared/IClient.h +++ b/extensions/src/ACRE2Shared/IClient.h @@ -29,6 +29,8 @@ class IClient { virtual acre::Result localStartSpeaking(const acre::Speaking speakingType, const std::string radioId) = 0; virtual acre::Result localStopSpeaking(const acre::Speaking speakingType) = 0; + virtual acre::id_t getClientIDByName(const std::string &targetClientName_) = 0; + virtual std::string getTempFilePath( void ) = 0; virtual std::string getConfigFilePath(void) = 0; diff --git a/extensions/src/ACRE2TS/TS3Client.cpp b/extensions/src/ACRE2TS/TS3Client.cpp index a351794a5..61b1f5dd1 100644 --- a/extensions/src/ACRE2TS/TS3Client.cpp +++ b/extensions/src/ACRE2TS/TS3Client.cpp @@ -674,3 +674,29 @@ acre::Result CTS3Client::updateShouldSwitchTS3Channel(const bool state_) { bool CTS3Client::shouldSwitchTS3Channel() { return getShouldSwitchTS3Channel(); } + +acre::id_t CTS3Client::getClientIDByName(const std::string &targetClientName_) { + acre::id_t targetID{0}; // cliendID of 0 is considered invalid and indicates that target was not found + + anyID clientId; + anyID *clientList; + uint32_t res = ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId); + if (res == ERROR_ok) { + res = ts3Functions.getClientList(ts3Functions.getCurrentServerConnectionHandlerID(), &clientList); + if (res == ERROR_ok) { + std::array clientName{""}; + size_t index = 0; + while ((targetID == 0) && (clientList[index] != 0)) { // "null terminated array of client ids like {10, 30, …, 0}" + res = ts3Functions.getClientDisplayName(ts3Functions.getCurrentServerConnectionHandlerID(), clientList[index], clientName.data(), clientName.size()); + if (res == ERROR_ok) { + if (targetClientName_ == std::string(clientName.data())) { + targetID = clientList[index]; + } + } + index++; + } + ts3Functions.freeMemory(clientList); // "caller must free the array" + } + } + return targetID; +} diff --git a/extensions/src/ACRE2TS/TS3Client.h b/extensions/src/ACRE2TS/TS3Client.h index 5a2cce9c2..898644497 100644 --- a/extensions/src/ACRE2TS/TS3Client.h +++ b/extensions/src/ACRE2TS/TS3Client.h @@ -60,6 +60,15 @@ class CTS3Client: public IClient { */ acre::Result localStopSpeaking(const acre::Speaking speakingType_ ); + /*! + * \brief Gets client id of a headless client + * + * \param[in] targetClientName_ Target's DisplayName in TS + * + * \return acre::id_t + */ + acre::id_t getClientIDByName(const std::string &targetClientName_); + std::string getTempFilePath( void ); std::string getConfigFilePath(void); From 05ae8e205c09dd1c9753f3ce24ce5e5c30b0ad0b Mon Sep 17 00:00:00 2001 From: PabstMirror Date: Wed, 19 May 2021 23:21:00 -0500 Subject: [PATCH 3/3] fix --- addons/sys_headless/XEH_postInit.sqf | 2 +- addons/sys_headless/fnc_addRadio.sqf | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sys_headless/XEH_postInit.sqf b/addons/sys_headless/XEH_postInit.sqf index 7e1117838..17728c027 100644 --- a/addons/sys_headless/XEH_postInit.sqf +++ b/addons/sys_headless/XEH_postInit.sqf @@ -13,7 +13,7 @@ if (hasInterface) then { TRACE_1("RPC STOP: mission end",_x); ["ext_remoteStopSpeaking", format ["%1,", _x]] call EFUNC(sys_rpc,callRemoteProcedure); } forEach (missionNamespace getVariable [QGVAR(idsToCleanup), []]) - }]; + }]; } ] call CBA_fnc_waitUntilAndExecute; }; diff --git a/addons/sys_headless/fnc_addRadio.sqf b/addons/sys_headless/fnc_addRadio.sqf index a9c19c37e..6ed56974a 100644 --- a/addons/sys_headless/fnc_addRadio.sqf +++ b/addons/sys_headless/fnc_addRadio.sqf @@ -12,7 +12,7 @@ * Added * * Example: - * [q2, "acre_prc343"] call acre_sys_headless_fnc_add + * [q2, "acre_prc343"] call acre_sys_headless_fnc_addRadio * * Public: No */