diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b17da04eb..f0ec9263d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -88,13 +88,13 @@ jobs: run: | cd extensions\\vcproj cmake .. -A Win32 - msbuild ACRE.sln /m '/t:ACRE2Arma\acre;ACRE2Arma\arma2ts;ACRE2\ACRE2Steam;ACRE2\ACRE2TS;Extras\Wav2B64' /p:Configuration=RelWithDebInfo + msbuild ACRE.sln /m '/t:ACRE2Arma\acre;ACRE2Arma\arma2ts;ACRE2\ACRE2Mumble;ACRE2\ACRE2Steam;ACRE2\ACRE2TS;Extras\Wav2B64' /p:Configuration=RelWithDebInfo - name: Build Extensions (x64) if: matrix.arch == 'x64' run: | cd extensions\\vcproj64 cmake .. -A x64 - msbuild ACRE.sln /m '/t:ACRE2Arma\acre;ACRE2Arma\arma2ts;ACRE2\ACRE2Steam;ACRE2\ACRE2TS' /p:Configuration=RelWithDebInfo + msbuild ACRE.sln /m '/t:ACRE2Arma\acre;ACRE2Arma\arma2ts;ACRE2\ACRE2Mumble;ACRE2\ACRE2Steam;ACRE2\ACRE2TS' /p:Configuration=RelWithDebInfo - name: Sign (BattlEye) run: | .\ci\battleye\signtool.exe sign /f .\ci\battleye\idi-systems.pfx /p ${{ secrets.BE_CRED_PASSWORD }} /t http://timestamp.digicert.com *.dll diff --git a/addons/main/script_macros.hpp b/addons/main/script_macros.hpp index 7d153bdbf..c8a225d8f 100644 --- a/addons/main/script_macros.hpp +++ b/addons/main/script_macros.hpp @@ -108,7 +108,7 @@ SQF equivalent of extensions/src/ACRE2Shared/Types.h #define SCRATCH_GET(radioId, key) ([radioId, key] call EFUNC(sys_data,getScratchData)) #define SCRATCH_GET_DEF(radioId, key, defaultVal) ([radioId, key, defaultVal] call EFUNC(sys_data,getScratchData)) -#define GET_TS3ID(object) (object call { private _ret = (_this getVariable [QGVAR(ts3id), -1]); if (_ret == -1) then { WARNING_1("%1 has no TS3 ID",_this); }; _ret }) +#define GET_VOIPID(object) (object call { private _ret = (_this getVariable [QGVAR(voipId), -1]); if (_ret == -1) then { WARNING_1("%1 has no VOIP ID",_this); }; _ret }) #define IS_HASH(hash) (hash isEqualType locationNull && {(type hash) isEqualTo "ACRE_FastHashNamespaceDummy"}) diff --git a/addons/main/script_version.hpp b/addons/main/script_version.hpp index aeb4be999..33bfd4658 100644 --- a/addons/main/script_version.hpp +++ b/addons/main/script_version.hpp @@ -1,4 +1,4 @@ #define MAJOR 2 #define MINOR 10 #define PATCHLVL 0 -#define BUILD 1050 +#define BUILD 1051 diff --git a/addons/sys_core/XEH_postInit.sqf b/addons/sys_core/XEH_postInit.sqf index 4f11d81dc..8d78b1ae9 100644 --- a/addons/sys_core/XEH_postInit.sqf +++ b/addons/sys_core/XEH_postInit.sqf @@ -5,7 +5,7 @@ if (!hasInterface) exitWith {}; -// Ensure the TeamSpeak plugin handler code is initialized first +// Ensure the VOIP plugin handler code is initialized first [] call EFUNC(sys_io,startServer); ["handleGetClientID", FUNC(handleGetClientID)] call EFUNC(sys_rpc,addProcedure); @@ -145,7 +145,7 @@ if (getClientStateNumber < 10) then { // Check before game has started (in brief ["setSoundSystemMasterOverride", [0]] call EFUNC(sys_rpc,callRemoteProcedure); [_this select 1] call CBA_fnc_removePerFrameHandler; } else { - // Keep calling incase ACRE is not connected to TeamSpeak + // Keep calling incase ACRE is not connected to VOIP solution ["setSoundSystemMasterOverride", [1]] call EFUNC(sys_rpc,callRemoteProcedure); }; }, 0, []] call CBA_fnc_addPerFrameHandler; diff --git a/addons/sys_core/XEH_preInit.sqf b/addons/sys_core/XEH_preInit.sqf index 910912fd2..ef4f1b6ac 100644 --- a/addons/sys_core/XEH_preInit.sqf +++ b/addons/sys_core/XEH_preInit.sqf @@ -25,13 +25,13 @@ DGVAR(lowered) = false; DGVAR(muting) = []; DGVAR(speakers) = []; DGVAR(enableDistanceMuting) = true; -DGVAR(ts3id) = -1; +DGVAR(voipId) = -1; DGVAR(keyedMicRadios) = []; DGVAR(keyedRadioIds) = HASH_CREATE; DGVAR(globalVolume) = 1.0; DGVAR(maxVolume) = 1.0; DGVAR(playerList) = []; // Paired array: [TS_ID, Object] -//DGVAR(ts3IdPlayerList) = []; +//DGVAR(voipIdPlayerList) = []; DGVAR(isDeaf) = false; //DGVAR(playerListHash) = HASH_CREATE; DGVAR(spectatorSpeakers) = []; diff --git a/addons/sys_core/fnc_coreInitPFH.sqf b/addons/sys_core/fnc_coreInitPFH.sqf index 81020dbab..4ffe42f45 100644 --- a/addons/sys_core/fnc_coreInitPFH.sqf +++ b/addons/sys_core/fnc_coreInitPFH.sqf @@ -19,9 +19,9 @@ if (isNull player) exitWith {}; acre_player = player; -if (!ACRE_MAP_LOADED || {!ACRE_DATA_SYNCED} || {GVAR(ts3id) == -1}) exitWith {}; +if (!ACRE_MAP_LOADED || {!ACRE_DATA_SYNCED} || {GVAR(voipId) == -1}) exitWith {}; -TRACE_1("GOT TS3 ID", GVAR(ts3id)); +TRACE_1("GOT VOIP ID", GVAR(voipId)); [] call FUNC(utilityFunction); // OK [] call FUNC(muting); diff --git a/addons/sys_core/fnc_cycleLanguage.sqf b/addons/sys_core/fnc_cycleLanguage.sqf index 14c00a2ff..482ad566c 100644 --- a/addons/sys_core/fnc_cycleLanguage.sqf +++ b/addons/sys_core/fnc_cycleLanguage.sqf @@ -28,7 +28,7 @@ if (_numSpokenLanguages > 1) then { ["acre_cycleLanguage", _languageName, "Now speaking", "", 1, EGVAR(sys_list,LanguageColor)] call EFUNC(sys_list,displayHint); [] call FUNC(updateSelf); if (ACRE_LOCAL_SPEAKING) then { - //@TODO: This is an uber hack, should probably be set up as a TS event. + //@TODO: This is an uber hack, should probably be set up as a Mumble/TS event. //Basically we update globally a locally set object variable from the //start speaking event when they cycle languages while talking. acre_player setVariable [QGVAR(languageId), _languageId, true]; diff --git a/addons/sys_core/fnc_getAlive.sqf b/addons/sys_core/fnc_getAlive.sqf index d54978208..59bdfc600 100644 --- a/addons/sys_core/fnc_getAlive.sqf +++ b/addons/sys_core/fnc_getAlive.sqf @@ -24,8 +24,8 @@ if (_unit isEqualTo acre_player) then { }; } else { if (!isNull _unit) then { - private _ts3id = GET_TS3ID(_unit); - if ((alive _unit && {!(_ts3id in ACRE_SPECTATORS_LIST)}) || {(ACRE_IS_SPECTATOR && {!ACRE_MUTE_SPECTATORS} && {_ts3id in ACRE_SPECTATORS_LIST})}) then { + private _voipId = GET_VOIPID(_unit); + if ((alive _unit && {!(_voipId in ACRE_SPECTATORS_LIST)}) || {(ACRE_IS_SPECTATOR && {!ACRE_MUTE_SPECTATORS} && {_voipId in ACRE_SPECTATORS_LIST})}) then { _ret = 1; }; }; diff --git a/addons/sys_core/fnc_getClientIdLoop.sqf b/addons/sys_core/fnc_getClientIdLoop.sqf index ca30d59d1..3abca9988 100644 --- a/addons/sys_core/fnc_getClientIdLoop.sqf +++ b/addons/sys_core/fnc_getClientIdLoop.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * This function exists to setup the process for sending our object and player ID to other clients to associate with our TeamSpeak ID. + * This function exists to setup the process for sending our object and player ID to other clients to associate with our VOIP ID. * * Arguments: * None diff --git a/addons/sys_core/fnc_handleGetClientID.sqf b/addons/sys_core/fnc_handleGetClientID.sqf index 4b0d84f17..5cddc5b33 100644 --- a/addons/sys_core/fnc_handleGetClientID.sqf +++ b/addons/sys_core/fnc_handleGetClientID.sqf @@ -1,11 +1,11 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Handles receipt of TS client ID alongside its objects network ID. + * Handles receipt of Mumble/TS client ID alongside its objects network ID. * * Arguments: - * 0: TS client ID - * 1: Net ID of object for TS client ID + * 0: Mumble/TS client ID + * 1: Net ID of object for Mumble/TS client ID * * Return Value: * Handled @@ -16,48 +16,48 @@ * Public: No */ -params ["_newTs3Id","_netId"]; +params ["_newVoipId", "_netId"]; -_newTs3Id = parseNumber _newTs3Id; +_newVoipId = parseNumber _newVoipId; private _playerObject = objectFromNetId _netId; TRACE_1("got client ID", _this); if (_playerObject == acre_player) then { private _resendSpectator = false; - if (_newTs3Id != GVAR(ts3id)) then { + if (_newVoipId != GVAR(voipId)) then { if (ACRE_IS_SPECTATOR) then { [] call FUNC(spectatorOff); _resendSpectator = true; }; }; - GVAR(ts3id) = _newTs3Id; + GVAR(voipId) = _newVoipId; if (_resendSpectator) then { [] call FUNC(spectatorOn) }; - TRACE_1("SETTING TS3ID",GVAR(ts3id)); + TRACE_1("SETTING VOIPID",GVAR(voipId)); } else { - _playerObject setVariable [QGVAR(ts3id), _newTs3Id, false]; + _playerObject setVariable [QGVAR(voipId), _newVoipId, false]; }; -//Ensure the incoming TS ID is pointing to the correct unit. +// Ensure the incoming Mumble/TS ID is pointing to the correct unit. private _found = false; { - _x params ["_remoteTs3Id","_remoteUser"]; - if (_remoteTs3Id == _newTs3Id) exitWith { + _x params ["_remoteVoipId", "_remoteUser"]; + if (_remoteVoipId == _newVoipId) exitWith { _found = true; if (_playerObject != _remoteUser) then { - GVAR(playerList) set [_forEachIndex, [_newTs3Id, _playerObject]]; + GVAR(playerList) set [_forEachIndex, [_newVoipId, _playerObject]]; //If changed remove. REM(GVAR(speakers),_remoteUser); - REM(GVAR(spectatorSpeakers),_remoteTs3Id); - REM(GVAR(godSpeakers),_remoteTs3Id); + REM(GVAR(spectatorSpeakers),_remoteVoipId); + REM(GVAR(godSpeakers),_remoteVoipId); REM(GVAR(keyedMicRadios),_remoteUser); }; - // Case where objects dont match but we found our TS ID. + // Case where objects dont match but we found our Mumble/TS ID. }; } forEach (GVAR(playerList)); if (!_found) then { - GVAR(playerList) pushBack [_newTs3Id,_playerObject]; + GVAR(playerList) pushBack [_newVoipId,_playerObject]; }; true diff --git a/addons/sys_core/fnc_handleGetHeadVector.sqf b/addons/sys_core/fnc_handleGetHeadVector.sqf index 831aa5afd..e930ab800 100644 --- a/addons/sys_core/fnc_handleGetHeadVector.sqf +++ b/addons/sys_core/fnc_handleGetHeadVector.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Sends the current head direction vector to the TeamSpeak plugin. + * Sends the current head direction vector to the VOIP plugin. * * Arguments: * None diff --git a/addons/sys_core/fnc_handleGetPluginVersion.sqf b/addons/sys_core/fnc_handleGetPluginVersion.sqf index 59cd6a1f9..9bec5fb85 100644 --- a/addons/sys_core/fnc_handleGetPluginVersion.sqf +++ b/addons/sys_core/fnc_handleGetPluginVersion.sqf @@ -1,10 +1,10 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Handles the return of the current plugin version from TeamSpeak. Displays messages to the player if any of the versions do not match. + * Handles the return of the current plugin version from VOIP. Displays messages to the player if any of the versions do not match. * * Arguments: - * 0: TeamSpeak plugin version + * 0: VOIP plugin version * * Return Value: * Handled @@ -35,7 +35,7 @@ if (!ACRE_SPIT_VERSION && {!isNil "ACRE_FULL_SERVER_VERSION"}) then { if (_warn) then { ACRE_HAS_WARNED = true; - ERROR_WITH_TITLE_3("Mismatched TeamSpeak plugin or mod versions!","\nTeamSpeak plugin version: %1\nYour version: %2\nServer version: %3",GVAR(pluginVersion),QUOTE(VERSION_STR),ACRE_FULL_SERVER_VERSION); + ERROR_WITH_TITLE_3("Mismatched VOIP plugin or mod versions!","\nVOIP plugin version: %1\nYour version: %2\nServer version: %3",GVAR(pluginVersion),QUOTE(VERSION_STR),ACRE_FULL_SERVER_VERSION); } else { if (ACRE_HAS_WARNED) then { ACRE_HAS_WARNED = false; diff --git a/addons/sys_core/fnc_isMuted.sqf b/addons/sys_core/fnc_isMuted.sqf index 93286bd6b..f6be1881a 100644 --- a/addons/sys_core/fnc_isMuted.sqf +++ b/addons/sys_core/fnc_isMuted.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * This function determines is used to check if the unit should be muted on TeamSpeak or not. + * This function determines is used to check if the unit should be muted on VOIP or not. * * Arguments: * 0: unit diff --git a/addons/sys_core/fnc_localStartSpeaking.sqf b/addons/sys_core/fnc_localStartSpeaking.sqf index 1db10fb6b..a4a74a5ae 100644 --- a/addons/sys_core/fnc_localStartSpeaking.sqf +++ b/addons/sys_core/fnc_localStartSpeaking.sqf @@ -48,7 +48,7 @@ if (!GVAR(fullDuplex) && {ACRE_BROADCASTING_RADIOID != ""}) then { private _unit = _x select 0; if (!isNull _unit && {_unit != acre_player}) then { private _canUnderstand = [_unit] call FUNC(canUnderstand); - ["updateSpeakingData", ["r", GET_TS3ID(_unit), !_canUnderstand, 1, 0, 1, 0, false, [0, 0, 0]]] call EFUNC(sys_rpc,callRemoteProcedure); + ["updateSpeakingData", ["r", GET_VOIPID(_unit), !_canUnderstand, 1, 0, 1, 0, false, [0, 0, 0]]] call EFUNC(sys_rpc,callRemoteProcedure); }; } forEach (_sources select _forEachIndex); }; diff --git a/addons/sys_core/fnc_localStopSpeaking.sqf b/addons/sys_core/fnc_localStopSpeaking.sqf index 1c52f82bf..47176f314 100644 --- a/addons/sys_core/fnc_localStopSpeaking.sqf +++ b/addons/sys_core/fnc_localStopSpeaking.sqf @@ -29,6 +29,6 @@ if (isNil "ACRE_CustomVolumeControl") then { EGVAR(sys_gui,volumeLevel) call EFUNC(sys_gui,setVoiceCurveLevel); }; -//[str GVAR(ts3id), netId acre_player] call FUNC(remoteStopSpeaking); +//[str GVAR(voipId), netId acre_player] call FUNC(remoteStopSpeaking); true diff --git a/addons/sys_core/fnc_muting.sqf b/addons/sys_core/fnc_muting.sqf index d0ae7f64b..258c24599 100644 --- a/addons/sys_core/fnc_muting.sqf +++ b/addons/sys_core/fnc_muting.sqf @@ -1,7 +1,8 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Sets up the per frame event handler to mute and unmute clients on TeamSpeak. The muting occurs to optimize TeamSpeak bandwidth as voice data is not sent for muted clients. + * Sets up the per frame event handler to mute and unmute clients on ^VOIP. The muting occurs to optimize VOIP + * bandwidth as voice data is not sent for muted clients. * * Arguments: * None @@ -39,15 +40,15 @@ DFUNC(mutingPFHLoop) = { _dynamicPos = positionCameraToWorld [0, 0, 0]; }; { - _x params ["_remoteTs3Id","_remoteUser"]; + _x params ["_remoteVoipId", "_remoteUser"]; if (_remoteUser != acre_player) then { private _muted = 0; - //private _remoteTs3Id = (_remoteUser getVariable QGVAR(ts3id)); - //if (!(isNil "_remoteTs3Id")) then { - if !(_remoteTs3Id in ACRE_SPECTATORS_LIST) then { + //private _remoteVoipId = (_remoteUser getVariable QGVAR(voipId)); + //if (!(isNil "_remoteVoipId")) then { + if !(_remoteVoipId in ACRE_SPECTATORS_LIST) then { private _isRemotePlayerAlive = [_remoteUser] call FUNC(getAlive); if (_isRemotePlayerAlive == 1) then { - //_playerIdList pushBack _remoteTs3Id; + //_playerIdList pushBack _remoteVoipId; // private _radioListRemote = [_remoteUser] call EFUNC(sys_data,getRemoteRadioList); // private _radioListLocal = [] call EFUNC(sys_data,getPlayerRadioList); @@ -61,10 +62,10 @@ DFUNC(mutingPFHLoop) = { }; if (GVAR(fullListTime)) then { - _mutingParams = _mutingParams + format ["%1,%2,", _remoteTs3Id, _muted]; + _mutingParams = _mutingParams + format ["%1,%2,", _remoteVoipId, _muted]; } else { if ((_muted == 0 && {_remoteUser in GVAR(muting)}) || {(_muted == 1 && {!(_remoteUser in GVAR(muting))})}) then { - _mutingParams = _mutingParams + format ["%1,%2,", _remoteTs3Id, _muted]; + _mutingParams = _mutingParams + format ["%1,%2,", _remoteVoipId, _muted]; }; }; }; @@ -82,7 +83,7 @@ DFUNC(mutingPFHLoop) = { }; if (_newSpectators isNotEqualTo []) then { { - if (_x != GVAR(ts3id)) then { + if (_x != GVAR(voipId)) then { _mutingParams = _mutingParams + format ["%1,1,", _x]; }; } forEach _newSpectators; @@ -94,7 +95,7 @@ DFUNC(mutingPFHLoop) = { if ((str ACRE_IS_SPECTATOR) != (str GVAR(lastSpectate))) then { if (ACRE_IS_SPECTATOR) then { { - if (_x != GVAR(ts3id)) then { + if (_x != GVAR(voipId)) then { _mutingParams = _mutingParams + format ["%1,0,", _x]; }; } forEach ACRE_SPECTATORS_LIST; @@ -105,7 +106,7 @@ DFUNC(mutingPFHLoop) = { if (ACRE_IS_SPECTATOR && {GVAR(fullListTime)}) then { { - if (_x != GVAR(ts3id)) then { + if (_x != GVAR(voipId)) then { _mutingParams = _mutingParams + format ["%1,0,", _x]; }; } forEach ACRE_SPECTATORS_LIST; diff --git a/addons/sys_core/fnc_pong.sqf b/addons/sys_core/fnc_pong.sqf index 5a801c980..f44184deb 100644 --- a/addons/sys_core/fnc_pong.sqf +++ b/addons/sys_core/fnc_pong.sqf @@ -1,7 +1,8 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Handler for recieving pong messages from the TeamSpeak plugin. This is called periodically as it provides a simple check to make sure TeamSpeak isis still connected to the game. + * Handler for recieving pong messages from the VOIP plugin. This is called periodically as it provides a + * simple check to make sure VOIP is still connected to the game. * * Arguments: * None diff --git a/addons/sys_core/fnc_processDirectSpeaker.sqf b/addons/sys_core/fnc_processDirectSpeaker.sqf index 0454c24f5..e5049b52a 100644 --- a/addons/sys_core/fnc_processDirectSpeaker.sqf +++ b/addons/sys_core/fnc_processDirectSpeaker.sqf @@ -1,13 +1,13 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Calculates the information required by TeamSpeak for a direct speech speaker. + * Calculates the information required by VOIP for a direct speech speaker. * * Arguments: * 0: Unit * * Return Value: - * Parameters to send to TeamSpeak + * Parameters to send to VOIP * * Example: * [unit] call acre_sys_core_fnc_processDirectSpeaker @@ -18,7 +18,7 @@ private ["_emitterPos", "_emitterDir"]; params ["_unit"]; -private _id = GET_TS3ID(_unit); +private _id = GET_VOIPID(_unit); private _bothSpectating = false; private _isIntercomAttenuate = false; diff --git a/addons/sys_core/fnc_processRadioSpeaker.sqf b/addons/sys_core/fnc_processRadioSpeaker.sqf index 81884d1d2..29d835631 100644 --- a/addons/sys_core/fnc_processRadioSpeaker.sqf +++ b/addons/sys_core/fnc_processRadioSpeaker.sqf @@ -1,14 +1,14 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Calculates the information required by TeamSpeak for a radio speaker. + * Calculates the information required by VOIP for a radio speaker. * * Arguments: * 0: Unit to process * 1: List of radio classnames * * Return Value: - * Parameters to send to TeamSpeak + * Parameters to send to VOIP * * Example: * [unit,["ACRE_PRC343_ID_1"]] call acre_sys_core_fnc_processRadioSpeaker diff --git a/addons/sys_core/fnc_remoteStartSpeaking.sqf b/addons/sys_core/fnc_remoteStartSpeaking.sqf index 53c746628..aedd9295a 100644 --- a/addons/sys_core/fnc_remoteStartSpeaking.sqf +++ b/addons/sys_core/fnc_remoteStartSpeaking.sqf @@ -4,7 +4,7 @@ * Handles the event of other (remote) players starting to speaking. * * Arguments: - * 0: TeamSpeak ID of talking player + * 0: VOIP ID of talking player * 1: Language ID * 2: Net ID of player object * 3: Speaking Type @@ -33,7 +33,7 @@ CREATE_COUNTER(hearableRadios); // PREP(processRadioSpeaker); TRACE_1("START SPEAKING ENTER", _this); -params ["_speakingId","_languageId","_netId","_speakingType",["_radioId",","]]; +params ["_speakingId", "_languageId", "_netId", "_speakingType", ["_radioId", ","]]; if (!(_speakingId isEqualType 0)) then { _speakingId = parseNumber _speakingId; }; if (!(_languageId isEqualType 0)) then { _languageId = parseNumber _languageId; }; @@ -53,20 +53,20 @@ private _result = false; //Ensure the incoming ID is solid. private _found = false; { - _x params ["_remoteTs3Id","_remoteUser"]; - if (_remoteTs3Id == _speakingId) exitWith { + _x params ["_remoteVoipId","_remoteUser"]; + if (_remoteVoipId == _speakingId) exitWith { _found = true; if (_unit != _remoteUser) then { GVAR(playerList) set [_forEachIndex, [_speakingId, _unit]]; REM(GVAR(speakers),_remoteUser); - REM(GVAR(spectatorSpeakers),_remoteTs3Id); - REM(GVAR(godSpeakers),_remoteTs3Id); + REM(GVAR(spectatorSpeakers),_remoteVoipId); + REM(GVAR(godSpeakers),_remoteVoipId); REM(GVAR(keyedMicRadios),_remoteUser); - /*if (_remoteTs3Id in ACRE_SPECTATORS_LIST) then { - GVAR(spectatorSpeakers) pushBackUnique _remoteTs3Id; + /*if (_remoteVoipId in ACRE_SPECTATORS_LIST) then { + GVAR(spectatorSpeakers) pushBackUnique _remoteVoipId; };*/ }; - // Case where objects dont match but we found our TS ID. + // Case where objects dont match but we found our Mumble/TS ID. }; } forEach (GVAR(playerList)); if (!_found) then { @@ -83,7 +83,7 @@ private _result = false; false }; - _unit setVariable [QGVAR(ts3id), _speakingId]; + _unit setVariable [QGVAR(voipId), _speakingId]; _unit setVariable [QGVAR(languageId), _languageId]; TRACE_1("unit pos", getPosASL _unit); private _isMuted = IS_MUTED(_unit); diff --git a/addons/sys_core/fnc_remoteStopSpeaking.sqf b/addons/sys_core/fnc_remoteStopSpeaking.sqf index d68efe1c6..d82214be7 100644 --- a/addons/sys_core/fnc_remoteStopSpeaking.sqf +++ b/addons/sys_core/fnc_remoteStopSpeaking.sqf @@ -4,7 +4,7 @@ * Handles the event of other (remote) players stopping speaking. * * Arguments: - * 0: TeamSpeak ID of talking player + * 0: VOIP ID of talking player * 1: Net ID of player object * * Return Value: @@ -18,7 +18,7 @@ params ["_speakingId","_netId"]; _speakingId = parseNumber _speakingId; -// if (_speakingId != GVAR(ts3id)) then { +// if (_speakingId != GVAR(voipId)) then { private _found = false; private _unit = objectFromNetId _netId; if (!isNil "_unit") then { diff --git a/addons/sys_core/fnc_setPluginSetting.sqf b/addons/sys_core/fnc_setPluginSetting.sqf index 1ab5c17a3..0ad2be705 100644 --- a/addons/sys_core/fnc_setPluginSetting.sqf +++ b/addons/sys_core/fnc_setPluginSetting.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Handles setting TeamSpeak plugin settings. Waits for pipe to open and then applies them. + * Handles setting VOIP plugin settings. Waits for pipe to open and then applies them. * * Arguments: * 0: Setting Name diff --git a/addons/sys_core/fnc_speaking.sqf b/addons/sys_core/fnc_speaking.sqf index 18d52a96a..236cd755e 100644 --- a/addons/sys_core/fnc_speaking.sqf +++ b/addons/sys_core/fnc_speaking.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Sets up the the per frame event handler for processing all the speaking data which in turn will send data to TeamSpeak. + * Sets up the the per frame event handler for processing all the speaking data which in turn will send data to VOIP. * * Arguments: * None @@ -218,7 +218,7 @@ if (GVAR(keyedMicRadios) isNotEqualTo []) then { _sentMicRadios pushBack _unit; private _params = HASH_GET(_compiledParams, _x); private _canUnderstand = [_unit] call FUNC(canUnderstand); - private _paramArray = ["r", GET_TS3ID(_unit), !_canUnderstand, count _params]; + private _paramArray = ["r", GET_VOIPID(_unit), !_canUnderstand, count _params]; _paramArray append (flatten _params); ["updateSpeakingData", _paramArray] call EFUNC(sys_rpc,callRemoteProcedure); }; @@ -245,12 +245,12 @@ if (GVAR(keyedMicRadios) isNotEqualTo []) then { ["updateSpeakingData", _params] call EFUNC(sys_rpc,callRemoteProcedure); } else { if !(_unit in _sentMicRadios) then { - ["updateSpeakingData", ["m", GET_TS3ID(_unit), 0]] call EFUNC(sys_rpc,callRemoteProcedure); + ["updateSpeakingData", ["m", GET_VOIPID(_unit), 0]] call EFUNC(sys_rpc,callRemoteProcedure); }; }; } else { if (_unit != acre_player) then { - ["updateSpeakingData", ['m', GET_TS3ID(_unit), 0]] call EFUNC(sys_rpc,callRemoteProcedure); + ["updateSpeakingData", ['m', GET_VOIPID(_unit), 0]] call EFUNC(sys_rpc,callRemoteProcedure); }; }; }; diff --git a/addons/sys_core/fnc_spectatorOff.sqf b/addons/sys_core/fnc_spectatorOff.sqf index 7c59c5d65..bb9de2b2d 100644 --- a/addons/sys_core/fnc_spectatorOff.sqf +++ b/addons/sys_core/fnc_spectatorOff.sqf @@ -17,7 +17,7 @@ if (ACRE_IS_SPECTATOR) then { ACRE_IS_SPECTATOR = false; - [QEGVAR(sys_server,onSetSpector), [GVAR(ts3id), 0, clientOwner]] call CALLSTACK(CBA_fnc_serverEvent); + [QEGVAR(sys_server,onSetSpector), [GVAR(voipId), 0, clientOwner]] call CALLSTACK(CBA_fnc_serverEvent); TRACE_1("spectatorOff",ACRE_IS_SPECTATOR); }; diff --git a/addons/sys_core/fnc_spectatorOn.sqf b/addons/sys_core/fnc_spectatorOn.sqf index 9c00a1c0a..0eb69a412 100644 --- a/addons/sys_core/fnc_spectatorOn.sqf +++ b/addons/sys_core/fnc_spectatorOn.sqf @@ -17,7 +17,7 @@ if (!ACRE_IS_SPECTATOR) then { ACRE_IS_SPECTATOR = true; - [QEGVAR(sys_server,onSetSpector), [GVAR(ts3id), 1, clientOwner]] call CALLSTACK(CBA_fnc_serverEvent); + [QEGVAR(sys_server,onSetSpector), [GVAR(voipId), 1, clientOwner]] call CALLSTACK(CBA_fnc_serverEvent); TRACE_1("spectatorOn",ACRE_IS_SPECTATOR); }; diff --git a/addons/sys_core/fnc_ts3idToPlayer.sqf b/addons/sys_core/fnc_voipIdToPlayer.sqf similarity index 72% rename from addons/sys_core/fnc_ts3idToPlayer.sqf rename to addons/sys_core/fnc_voipIdToPlayer.sqf index fe61086b8..68eba0eaa 100644 --- a/addons/sys_core/fnc_ts3idToPlayer.sqf +++ b/addons/sys_core/fnc_voipIdToPlayer.sqf @@ -1,16 +1,16 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Retrieves the game object of a player from a TeamSpeak ID. + * Retrieves the game object of a player from a VOIP ID. * * Arguments: - * 0: TeamSpeak ID + * 0: VOIP ID * * Return Value: * Player * * Example: - * ["1"] call acre_sys_core_fnc_ts3idToPlayer + * ["1"] call acre_sys_core_fnc_voipIdToPlayer * * Public: No */ diff --git a/addons/sys_core/initSettings.sqf b/addons/sys_core/initSettings.sqf index 33ac2f6cd..8aa04a81c 100644 --- a/addons/sys_core/initSettings.sqf +++ b/addons/sys_core/initSettings.sqf @@ -56,38 +56,38 @@ {["disableUnmuteClients", _this] call FUNC(setPluginSetting)} ] call CBA_fnc_addSetting; -// Teamspeak Channel Switching +// VOIP Channel Switching // Switch channels [ - QGVAR(ts3ChannelSwitch), + QGVAR(voipChannelSwitch), "CHECKBOX", - localize LSTRING(ts3ChannelSwitch_displayName), + localize LSTRING(voipChannelSwitch_displayName), "ACRE2", true, false, - {["disableTS3ChannelSwitch", _this] call FUNC(setPluginSetting)} + {["disableVoipChannelSwitch", _this] call FUNC(setPluginSetting)} ] call CBA_fnc_addSetting; // Channel Name [ - QGVAR(ts3ChannelName), + QGVAR(voipChannelName), "EDITBOX", - localize LSTRING(ts3ChannelName_displayName), + localize LSTRING(voipChannelName_displayName), "ACRE2", "", true, - {if (!isNull (findDisplay 46)) then {call EFUNC(sys_io,ts3ChannelMove)};} + {if (!isNull (findDisplay 46)) then {call EFUNC(sys_io,voipChannelMove)};} ] call CBA_fnc_addSetting; // Channel Password [ - QGVAR(ts3ChannelPassword), + QGVAR(voipChannelPassword), "EDITBOX", - localize LSTRING(ts3ChannelPassword_displayName), + localize LSTRING(voipChannelPassword_displayName), "ACRE2", ["", true], true, - {if (!isNull (findDisplay 46)) then {call EFUNC(sys_io,ts3ChannelMove)};} + {if (!isNull (findDisplay 46)) then {call EFUNC(sys_io,voipChannelMove)};} ] call CBA_fnc_addSetting; // Difficulty settings diff --git a/addons/sys_core/stringtable.xml b/addons/sys_core/stringtable.xml index d18441442..6543ae635 100644 --- a/addons/sys_core/stringtable.xml +++ b/addons/sys_core/stringtable.xml @@ -99,13 +99,25 @@ Desmutar Clientes Diğer Client'ları Susturma - - TeamSpeak Channel Switch + + Mumble/TeamSpeak Channel Switch Смена ТимСпик канала - TeamSpeak チャンネル切り替え - Teamspeak頻道切換 - Teamspeak频道切换 + Mumble/TeamSpeak チャンネル切り替え + Mumble/Teamspeak頻道切換 + Mumble/Teamspeak频道切换 팀스피크 채널 변경 + Trocar de Canal do Mumble/TeamSpeak + Zmiana kanału Mumble/TeamSpeak + Cambio canale Mumble/TeamSpeak + Cambio de canal de Mumble/TeamSpeak + Changer de canal Mumble/TeamSpeak + Mumble/TeamSpeak Kanalwechsel + + + Mumble/TeamSpeak Channel Name + Mumble/TeamSpeak チャンネル名 + Mumble/Teamspeak頻道名稱 + Mumble/Teamspeak频道名称 Trocar para Canal do TeamSpeak Zmiana kanału TeamSpeak Cambio canale TeamSpeak @@ -120,26 +132,26 @@ Teamspeak頻道名稱 Teamspeak频道名称 팀스피크 채널 이름 - Nome do Canal do TeamSpeak - Nazwa kanału TeamSpeak - Nome canale Teamspeak - Nombre de canal de TeamSpeak - Nom du canal TeamSpeak - TeamSpeak Kanalname + Nome do Canal do Mumble/TeamSpeak + Nazwa kanału Mumble/TeamSpeak + Nome canale Mumble/Teamspeak + Nombre de canal de Mumble/TeamSpeak + Nom du canal Mumble/TeamSpeak + Mumble/TeamSpeak Kanalname Имя ТимСпик канала TeamSpeak Kanal İsmi - - TeamSpeak Channel Password - Teamspeak頻道密碼 - Teamspeak频道密码 - Senha do Canal do TeamSpeak - Hasło kanału TeamSpeak - Password canale Teamspeak - TeamSpeak チャンネルのパスワード - Contraseña del canal de TeamSpeak - Mot de passe du canal TeamSpeak - TeamSpeak Kanalpasswort + + Mumble/TeamSpeak Channel Password + Mumble/Teamspeak頻道密碼 + Mumble/Teamspeak频道密码 + Senha do Canal do Mumble/TeamSpeak + Hasło kanału Mumble/TeamSpeak + Password canale Mumble/Teamspeak + Mumble/TeamSpeak チャンネルのパスワード + Contraseña del canal de Mumble/TeamSpeak + Mot de passe du canal Mumble/TeamSpeak + Mumble/TeamSpeak Kanalpasswort Пароль ТимСпик канала Teamspeak Kanal Parolası diff --git a/addons/sys_godmode/XEH_postInit.sqf b/addons/sys_godmode/XEH_postInit.sqf index 0b9d4084f..f8a11ee4e 100644 --- a/addons/sys_godmode/XEH_postInit.sqf +++ b/addons/sys_godmode/XEH_postInit.sqf @@ -12,7 +12,7 @@ LOAD_SOUND(Acre_GodPingOff); params ["_speakingId", "_speakingName", "_channel", "_channelEx"]; #ifndef TEST_SELF_RX - if (_speakingId == EGVAR(sys_core,ts3id)) exitWith {}; + if (_speakingId == EGVAR(sys_core,voipId)) exitWith {}; #endif GVAR(speakingGods) pushBackUnique _speakingId; diff --git a/addons/sys_godmode/fnc_handlePttKeyPress.sqf b/addons/sys_godmode/fnc_handlePttKeyPress.sqf index 68927294c..5275c53b3 100644 --- a/addons/sys_godmode/fnc_handlePttKeyPress.sqf +++ b/addons/sys_godmode/fnc_handlePttKeyPress.sqf @@ -79,7 +79,7 @@ GVAR(targetUnits) = GVAR(targetUnits) apply { }; }; -[QGVAR(startSpeaking), [EGVAR(sys_core,ts3id), profileName, _channel, _channelEx], GVAR(targetUnits)] call CBA_fnc_targetEvent; +[QGVAR(startSpeaking), [EGVAR(sys_core,voipId), profileName, _channel, _channelEx], GVAR(targetUnits)] call CBA_fnc_targetEvent; ["startGodModeSpeaking", ""] call EFUNC(sys_rpc,callRemoteProcedure); GVAR(speaking) = true; diff --git a/addons/sys_godmode/fnc_handlePttKeyPressUp.sqf b/addons/sys_godmode/fnc_handlePttKeyPressUp.sqf index e56d32d35..8596a668e 100644 --- a/addons/sys_godmode/fnc_handlePttKeyPressUp.sqf +++ b/addons/sys_godmode/fnc_handlePttKeyPressUp.sqf @@ -19,7 +19,7 @@ params ["_group"]; if !([_group] call FUNC(accessAllowed)) exitWith { false }; -[QGVAR(stopSpeaking), [EGVAR(sys_core,ts3id)], GVAR(targetUnits)] call CBA_fnc_targetEvent; +[QGVAR(stopSpeaking), [EGVAR(sys_core,voipId)], GVAR(targetUnits)] call CBA_fnc_targetEvent; GVAR(targetUnits) = []; ["stopGodModeSpeaking", ""] call EFUNC(sys_rpc,callRemoteProcedure); diff --git a/addons/sys_intercom/fnc_handleBroadcasting.sqf b/addons/sys_intercom/fnc_handleBroadcasting.sqf index 6f8e9ea56..00f06281d 100644 --- a/addons/sys_intercom/fnc_handleBroadcasting.sqf +++ b/addons/sys_intercom/fnc_handleBroadcasting.sqf @@ -37,7 +37,7 @@ private _changes = false; // Only broadcast if changes have been made. // TODO: Remove synchronisation once intercom system has been converted to components and unique IDs. -// It will help in reduce the bandwith, since information will be exchanged through the TS plugin. +// It will help in reduce the bandwith, since information will be exchanged through the Mumble/TS plugin. if (_changes) then { [_vehicle, _unit] call FUNC(updateVehicleInfoText); _vehicle setVariable [QGVAR(broadcasting), _broadcastConfig, true]; diff --git a/addons/sys_intercom/fnc_handleIntercomActivation.sqf b/addons/sys_intercom/fnc_handleIntercomActivation.sqf index 6876e7637..4f17086d7 100644 --- a/addons/sys_intercom/fnc_handleIntercomActivation.sqf +++ b/addons/sys_intercom/fnc_handleIntercomActivation.sqf @@ -19,7 +19,7 @@ params ["_unit", "_voiceActivation"]; // TODO: Remove synchronisation once intercom system has been converted to components and unique IDs. -// It will help in reduce the bandwith, since information will be exchanged through the TS plugin. +// It will help in reduce the bandwith, since information will be exchanged through the Mumble/TS plugin. _unit setVariable [QGVAR(intercomPTT), _voiceActivation, true]; GVAR(intercomPttKey) = _voiceActivation; diff --git a/addons/sys_io/XEH_PREP.hpp b/addons/sys_io/XEH_PREP.hpp index f2e9aff20..d58df89b0 100644 --- a/addons/sys_io/XEH_PREP.hpp +++ b/addons/sys_io/XEH_PREP.hpp @@ -4,4 +4,4 @@ PREP(startServer); PREP(serverReadLoop); PREP(server); PREP(ping); -PREP(ts3ChannelMove); +PREP(voipChannelMove); diff --git a/addons/sys_io/fnc_missionDisplayLoad.sqf b/addons/sys_io/fnc_missionDisplayLoad.sqf index 06d353a34..f323bad62 100644 --- a/addons/sys_io/fnc_missionDisplayLoad.sqf +++ b/addons/sys_io/fnc_missionDisplayLoad.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Move TeamSpeak 3 channel on main display load. + * Move VOIP channel on main display load. * * Arguments: * None @@ -16,6 +16,6 @@ */ if (GVAR(serverStarted) && {isMultiplayer}) then { - // Move TeamSpeak 3 channel on display 46 (main display) initialization - call FUNC(ts3ChannelMove); + // Move VOIP channel on display 46 (main display) initialization + call FUNC(voipChannelMove); }; diff --git a/addons/sys_io/fnc_ping.sqf b/addons/sys_io/fnc_ping.sqf index 848ff13e3..41f793a91 100644 --- a/addons/sys_io/fnc_ping.sqf +++ b/addons/sys_io/fnc_ping.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Creates a loop to send periodic messages to the TeamSpeak plugin (via the ACRE2Arma extension) to indicate that the game is still connected. + * Creates a loop to send periodic messages to the VOIP plugin (via the ACRE2Arma extension) to indicate that the game is still connected. * * Arguments: * None @@ -17,7 +17,7 @@ DFUNC(pingFunc) = { if (GVAR(serverStarted)) then { - LOG("ARMA2 TO TS3: PING!"); + LOG("ARMA3 TO VOIP: PING!"); // diag_log text format["%1 ACRE: ping!", diag_tickTime]; private _ret = "ACRE2Arma" callExtension "2ping:"; if (diag_tickTime - GVAR(pongTime) > 10) then { diff --git a/addons/sys_io/fnc_sendMessage.sqf b/addons/sys_io/fnc_sendMessage.sqf index caf01fe67..4e2151d4b 100644 --- a/addons/sys_io/fnc_sendMessage.sqf +++ b/addons/sys_io/fnc_sendMessage.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Sends a message to the TeamSpeak plugin via the ACRE2Arma extension. + * Sends a message to the VOIP plugin via the ACRE2Arma extension. * * Arguments: * 0: Message diff --git a/addons/sys_io/fnc_server.sqf b/addons/sys_io/fnc_server.sqf index 75f8a780f..34a45fb43 100644 --- a/addons/sys_io/fnc_server.sqf +++ b/addons/sys_io/fnc_server.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Creates a PFH to monitor the ACRE2Arma extension's connection to the TeamSpeak plugin. + * Creates a PFH to monitor the ACRE2Arma extension's connection to the VOIP plugin. * * Arguments: * None @@ -54,9 +54,9 @@ DFUNC(connectionFnc) = { INFO("Pipe opened."); GVAR(serverStarted) = true; - // Move TeamSpeak 3 channel if already in-game (otherwise display XEH will take care of it) + // Move VOIP channel if already in-game (otherwise display XEH will take care of it) if (!isNull (findDisplay 46)) then { - call FUNC(ts3ChannelMove); + call FUNC(voipChannelMove); }; }; }; diff --git a/addons/sys_io/fnc_serverReadLoop.sqf b/addons/sys_io/fnc_serverReadLoop.sqf index 98f84e5e2..a32172d78 100644 --- a/addons/sys_io/fnc_serverReadLoop.sqf +++ b/addons/sys_io/fnc_serverReadLoop.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Checks if the ACRE2Arma extension has any pending messages (typically for return data from the TeamSpeak plugin). This is called on a per frame basis. + * Checks if the ACRE2Arma extension has any pending messages (typically for return data from the VOIP plugin). This is called on a per frame basis. * * Arguments: * None diff --git a/addons/sys_io/fnc_startServer.sqf b/addons/sys_io/fnc_startServer.sqf index 00526f23a..ebef20b60 100644 --- a/addons/sys_io/fnc_startServer.sqf +++ b/addons/sys_io/fnc_startServer.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Starts the SQF code to maintain connectivity to the TeamSpeak plugin via the ACRE2Arma extension. + * Starts the SQF code to maintain connectivity to the VOIP plugin via the ACRE2Arma extension. * * Arguments: * None diff --git a/addons/sys_io/fnc_ts3ChannelMove.sqf b/addons/sys_io/fnc_ts3ChannelMove.sqf deleted file mode 100644 index f8bbe5d9f..000000000 --- a/addons/sys_io/fnc_ts3ChannelMove.sqf +++ /dev/null @@ -1,20 +0,0 @@ -#include "script_component.hpp" -/* - * Author: Tim Beswick - * Sends configured channel name and server name to TS plugin, triggering TeamSpeak 3 channel move. - * - * Arguments: - * None - * - * Return Value: - * None - * - * Example: - * [] call acre_sys_io_fnc_ts3ChannelMove - * - * Public: No - */ - -private _ts3ChannelDetails = format ["%1,%2,%3", EGVAR(sys_core,ts3ChannelName), EGVAR(sys_core,ts3ChannelPassword), serverName]; -TRACE_1("Moving TS3 Channel",_ts3ChannelDetails); -["setTs3ChannelDetails",_ts3ChannelDetails] call EFUNC(sys_rpc,callRemoteProcedure); diff --git a/addons/sys_io/fnc_voipChannelMove.sqf b/addons/sys_io/fnc_voipChannelMove.sqf new file mode 100644 index 000000000..f671d11f4 --- /dev/null +++ b/addons/sys_io/fnc_voipChannelMove.sqf @@ -0,0 +1,20 @@ +#include "script_component.hpp" +/* + * Author: Tim Beswick + * Sends configured channel name and server name to VOI^P plugin, triggering VOIP channel move. + * + * Arguments: + * None + * + * Return Value: + * None + * + * Example: + * [] call acre_sys_io_fnc_voipChannelMove + * + * Public: No + */ + +private _channelDetails = format ["%1,%2,%3", EGVAR(sys_core,voipChannelName), EGVAR(sys_core,voipChannelPassword), serverName]; +TRACE_1("Moving VOIP Channel",_channelDetails); +["setChannelDetails",_channelDetails] call EFUNC(sys_rpc,callRemoteProcedure); diff --git a/addons/sys_io/stringtable.xml b/addons/sys_io/stringtable.xml index 2342463f5..0dd0eef29 100644 --- a/addons/sys_io/stringtable.xml +++ b/addons/sys_io/stringtable.xml @@ -28,17 +28,17 @@ ACRE kapalı tünel hatasından başarıyla kurtuldu! - ACRE is not connected to TeamSpeak! - ACRE no está connectado al TeamSpeak! - ACRE ist nicht mit TeamSpeak verbunden! - ACRE non è connesso a TeamSpeak! - ACRE не подключен к TeamSpeak! - ACRE が TeamSpeak に接続されていません! - ACRE 尚未與TeamSpeak連接! - ACRE n'est pas connecté à TeamSpeak ! - ACRE 尚未与TeamSpeak连接! - ACRE não está conecato ao TeamSpeak! - ACRE TeamSpeak'e bağlı değil! + ACRE is not connected to Mumble/TeamSpeak! + ACRE no está connectado al Mumble/TeamSpeak! + ACRE ist nicht mit Mumble/TeamSpeak verbunden! + ACRE non è connesso a Mumble/TeamSpeak! + ACRE не подключен к Mumble/TeamSpeak! + ACRE が Mumble/TeamSpeak に接続されていません! + ACRE 尚未與Mumble/TeamSpeak連接! + ACRE n'est pas connecté à Mumble/TeamSpeak ! + ACRE 尚未与Mumble/TeamSpeak连接! + ACRE não está conecato ao Mumble/TeamSpeak! + ACRE Mumble/TeamSpeak'e bağlı değil! ACRE connected diff --git a/addons/sys_rpc/fnc_addProcedure.sqf b/addons/sys_rpc/fnc_addProcedure.sqf index 005e04d6e..9d19eac2c 100644 --- a/addons/sys_rpc/fnc_addProcedure.sqf +++ b/addons/sys_rpc/fnc_addProcedure.sqf @@ -1,7 +1,8 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Adds a procedure for when a message is received from the teamspeak plugin. Note only one callback exists before procedure this will override any previously setup callbacks for the specified procedure name. + * Adds a procedure for when a message is received from the VOIP plugin. Note only one callback exists + * before procedure this will override any previously setup callbacks for the specified procedure name. * * Arguments: * 0: Procedure name diff --git a/addons/sys_rpc/fnc_callRemoteProcedure.sqf b/addons/sys_rpc/fnc_callRemoteProcedure.sqf index 3e9532dfb..a83b046ab 100644 --- a/addons/sys_rpc/fnc_callRemoteProcedure.sqf +++ b/addons/sys_rpc/fnc_callRemoteProcedure.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Calls a procedure in the teamspeak plugin. + * Calls a procedure in the VOIP plugin. * * Arguments: * 0: Procedure name diff --git a/addons/sys_rpc/fnc_handleData.sqf b/addons/sys_rpc/fnc_handleData.sqf index 01a84ac7f..2b56a7526 100644 --- a/addons/sys_rpc/fnc_handleData.sqf +++ b/addons/sys_rpc/fnc_handleData.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Handles the receipt of messages from the teamspeak plugin. Firstly by de-serializing the recieved message. It will then call any procedure handlers. + * Handles the receipt of messages from the VOIP plugin. Firstly by de-serializing the recieved message. It will then call any procedure handlers. * * Arguments: * 0: Message diff --git a/addons/sys_server/XEH_postInitServer.sqf b/addons/sys_server/XEH_postInitServer.sqf index 3ee0107ea..a70b6f629 100644 --- a/addons/sys_server/XEH_postInitServer.sqf +++ b/addons/sys_server/XEH_postInitServer.sqf @@ -23,5 +23,5 @@ ACRE_FULL_SERVER_VERSION = QUOTE(VERSION_STR); publicVariable "ACRE_FULL_SERVER_VERSION"; -// Event handler to remove disconnected clients from the spectator TeamSpeak list +// Event handler to remove disconnected clients from the spectator VOIP list addMissionEventHandler ["PlayerDisconnected", {_this call FUNC(handlePlayerDisconnected);}]; diff --git a/addons/sys_server/XEH_preInitServer.sqf b/addons/sys_server/XEH_preInitServer.sqf index 815c7a2fd..6bc31a440 100644 --- a/addons/sys_server/XEH_preInitServer.sqf +++ b/addons/sys_server/XEH_preInitServer.sqf @@ -11,7 +11,7 @@ GVAR(masterIdList) = []; GVAR(markedForGC) = HASH_CREATE; //Entry format key: radioId value: [time_last_recieved,time_last_gc_find,object] -DVAR(ACRE_SPECTATORS_LIST) = []; // TeamSpeak 3 IDs of players spectating +DVAR(ACRE_SPECTATORS_LIST) = []; // VOIP IDs of players spectating DVAR(ACRE_SPECTATORS_A3_CLIENT_ID_LIST) = []; // clientOwner IDs of players spectating GVAR(masterIdTable) = HASH_CREATE; diff --git a/addons/sys_server/fnc_setSpectator.sqf b/addons/sys_server/fnc_setSpectator.sqf index ca69e8b1e..326697a17 100644 --- a/addons/sys_server/fnc_setSpectator.sqf +++ b/addons/sys_server/fnc_setSpectator.sqf @@ -4,7 +4,7 @@ * Updates the ACRE_SPECTATORS_LIST global variable. * * Arguments: - * 0: TeamSpeak ID + * 0: VOIP ID * 1: Specatator status (1 = on, 0 = off) * 2: Arma 3 Client ID * @@ -18,18 +18,18 @@ */ if (isServer) then { - params ["_ts3Id","_tsStatus","_clientOwner"]; + params ["_voipId", "_voipStatus", "_clientOwner"]; private _preCount = count ACRE_SPECTATORS_LIST; - if (_tsStatus == 1) then { - if (ACRE_SPECTATORS_LIST pushBackUnique _ts3Id != -1) then { + if (_voipStatus == 1) then { + if ((ACRE_SPECTATORS_LIST pushBackUnique _voipId) != -1) then { ACRE_SPECTATORS_A3_CLIENT_ID_LIST pushBack _clientOwner; }; } else { - private _idx = ACRE_SPECTATORS_LIST find _ts3Id; + private _idx = ACRE_SPECTATORS_LIST find _voipId; while {_idx != -1} do { ACRE_SPECTATORS_LIST deleteAt _idx; ACRE_SPECTATORS_A3_CLIENT_ID_LIST deleteAt _idx; - _idx = ACRE_SPECTATORS_LIST find _ts3Id; + _idx = ACRE_SPECTATORS_LIST find _voipId; }; }; // Only call publicVariable if array changes. diff --git a/addons/sys_sounds/fnc_handleLoadedSound.sqf b/addons/sys_sounds/fnc_handleLoadedSound.sqf index 681c7080e..46142d1a2 100644 --- a/addons/sys_sounds/fnc_handleLoadedSound.sqf +++ b/addons/sys_sounds/fnc_handleLoadedSound.sqf @@ -1,7 +1,8 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * This is the callback function for recieving the event "handleLoadedSound" from teamspeak. It just ensures a sound is marked as loaded to prevent it from being loaded again. It will also call the callback function specified in the loadSound function if one exists. + * This is the callback function for recieving the event "handleLoadedSound" from VOIP plugin. It just ensures a sound is marked as + * loaded to prevent it from being loaded again. It will also call the callback function specified in the loadSound function if one exists. * * Arguments: * 0: Radio classname diff --git a/addons/sys_sounds/fnc_loadSound.sqf b/addons/sys_sounds/fnc_loadSound.sqf index 65635dc0a..fbb5e11d5 100644 --- a/addons/sys_sounds/fnc_loadSound.sqf +++ b/addons/sys_sounds/fnc_loadSound.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Function to send a b64 sound to the teamspeak plugin to be loaded. Once loaded the sound will always be available. + * Function to send a b64 sound to the VOIP plugin to be loaded. Once loaded the sound will always be available. * * Arguments: * 0: Sound classname (in CfgAcreSounds) @@ -19,8 +19,8 @@ params ["_className",["_returnFunction",nil],["_force",false]]; -// If teamspeak is connected. -if (EGVAR(sys_core,ts3id) != -1) then { +// If VOIP plugin is connected. +if (EGVAR(sys_core,voipId) != -1) then { if (!(_className in GVAR(loadedSounds)) && {!_force}) then { private _fileName = getText(configFile >> "CfgAcreSounds" >> _className >> "sound"); if (_fileName != "") then { @@ -42,7 +42,7 @@ if (EGVAR(sys_core,ts3id) != -1) then { //teamspeak is not connected store in a buffer. if (isNil QGVAR(soundLoadBuffer)) then { GVAR(soundLoadBuffer) = [_this]; - [{EGVAR(sys_core,ts3id) != -1}, { + [{EGVAR(sys_core,voipId) != -1}, { { _x call FUNC(loadSound); } forEach (GVAR(soundLoadBuffer)); diff --git a/addons/sys_sounds/fnc_playSound.sqf b/addons/sys_sounds/fnc_playSound.sqf index 392140bbc..140e2cc37 100644 --- a/addons/sys_sounds/fnc_playSound.sqf +++ b/addons/sys_sounds/fnc_playSound.sqf @@ -1,7 +1,7 @@ #include "script_component.hpp" /* * Author: ACRE2Team - * Plays a loaded sound from teamspeak. + * Plays a loaded sound from VOIP solution. * * Arguments: * 0: Sound classname - Same classname as used in the loadSound function. diff --git a/docs/wiki/development/building.md b/docs/wiki/development/building.md index fea3f7ce8..77e01bbd3 100644 --- a/docs/wiki/development/building.md +++ b/docs/wiki/development/building.md @@ -112,7 +112,7 @@ To start the game using this build, you can use the following modline: -mod=@CBA_A3;idi\acre ``` -You will also need `acre.dll` and `ACRE2Arma.dll` (or `acre_x64.dll` and `ACRE2Arma_x64.dll` for 64-bit) placed in `idi\acre` folder. You can obtain those from last release or [build them yourself](building-extensions). TeamSpeak plugin is not required for basic operation. +You will also need `acre.dll` and `ACRE2Arma.dll` (or `acre_x64.dll` and `ACRE2Arma_x64.dll` for 64-bit) placed in `idi\acre` folder. You can obtain those from last release or [build them yourself](building-extensions). Mumble/TeamSpeak plugin is not required for basic operation. ### Create a Release Build diff --git a/docs/wiki/development/tips-and-tricks.md b/docs/wiki/development/tips-and-tricks.md index 0afa42848..1e187fdfe 100644 --- a/docs/wiki/development/tips-and-tricks.md +++ b/docs/wiki/development/tips-and-tricks.md @@ -14,8 +14,24 @@ Reference [Radio Signal Debugging](/wiki/frameworks/radio-signal-debugging) page ## Extensions -### TeamSpeak 3 +### VOIP **Skip Plugin Auto-Copy** Run Arma 3 with `-skipAcrePluginCopy` startup parameter. + +#### TeamSpeak 3 + +**Skip TeamSpeak Plugin Auto-Copy** + +Run Arma 3 with `-skipAcreTSPluginCopy` startup parameter. + +#### Mumble + +**Skip Mumble Plugin Auto-Copy** + +Run Arma 3 with `-skipAcreMumblePluginCopy` startup parameter. + +**Specify Mumble Path** + +Run Arma 3 with `-mumblePath=` startup parameter. diff --git a/docs/wiki/user/installation.md b/docs/wiki/user/installation.md index 0e035e7e1..a1f43bba6 100644 --- a/docs/wiki/user/installation.md +++ b/docs/wiki/user/installation.md @@ -8,8 +8,8 @@ title: Installation - Subscribe to [ACRE2 on Steam](http://steamcommunity.com/sharedfiles/filedetails/?id=751965892) (make sure you are also subscribed to [CBA_A3](https://steamcommunity.com/sharedfiles/filedetails/?id=450814997)). - Launch Arma 3. -- ACRE2 will try to copy the plugins to your TeamSpeak 3 installation directory. A pop-up will appear describing what the process did. -- Launch TeamSpeak 3 and assure the ACRE2 plugin is enabled in the `Settings -> Plugins` window. +- ACRE2 will try to copy the plugins to your Mumble/TeamSpeak 3 installation directory. A pop-up will appear describing what the process did. +- Launch Mumble/TeamSpeak 3 and assure the ACRE2 plugin is enabled in the `Settings -> Plugins` window. ### Manual @@ -25,15 +25,15 @@ title: Installation - In the dialogue that appears, navigate to your Arma 3 installation folder and select the `@acre2` folder. - Enable the mod. - Launch Arma 3 in the Launcher. -- ACRE2 will try to copy the plugins to your TeamSpeak 3 installation directory. A pop-up will appear describing what the process did. -- Launch TeamSpeak 3 and assure the ACRE2 plugin is enabled in the `Settings -> Plugins` window. +- ACRE2 will try to copy the plugins to your Mumble/TeamSpeak 3 installation directory. A pop-up will appear describing what the process did. +- Launch Mumble/TeamSpeak 3 and assure the ACRE2 plugin is enabled in the `Settings -> Plugins` window. #### Shortcut - Create a new shortcut for Arma 3, or edit an existing one, and add `@acre2` and `@CBA_A3` to the `-mod` parameter. - Launch Arma 3 through the shortcut you created. -- ACRE2 will try to copy the plugins to your TeamSpeak 3 installation directory. A pop-up will appear describing what the process did. -- Launch TeamSpeak 3 and assure the ACRE2 plugin is enabled in the `Settings -> Plugins` window. +- ACRE2 will try to copy the plugins to your Mumble/TeamSpeak 3 installation directory. A pop-up will appear describing what the process did. +- Launch Mumble/TeamSpeak 3 and assure the ACRE2 plugin is enabled in the `Settings -> Plugins` window. ### Configuration @@ -45,8 +45,8 @@ Server administrators can get available settings using the [export/import featur ### Troubleshooting -- **WARNING: ACRE IS NOT CONNECTED TO TEAMSPEAK!** -Make sure to launch both Arma 3 (Steam) and TeamSpeak 3 as admin, or neither. +- **WARNING: ACRE is not connected to Mumble/TeamSpeak!** +Make sure to launch both Arma 3 (Steam) and Mumble/TeamSpeak 3 as admin, or neither. - **Radio beeps not audible on dedicated server** Make sure your `server.cfg` has `"b64"` whitelisted in `allowedLoadFileExtensions[]` and `allowedPreprocessFileExtensions[]` if you are using that. Let your server administrator know if you have no idea what this means. diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index 6ab9abf4d..03d9dc0f2 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -69,6 +69,7 @@ add_subdirectory(src/ACRE2Arma) add_subdirectory(src/ACRE2Steam) add_subdirectory(src/ACRE2TS) +add_subdirectory(src/ACRE2Mumble) #Extras add_subdirectory(src/Wav2B64) diff --git a/extensions/src/ACRE2Core/AcreSettings.cpp b/extensions/src/ACRE2Core/AcreSettings.cpp index b6a341188..090770e7f 100644 --- a/extensions/src/ACRE2Core/AcreSettings.cpp +++ b/extensions/src/ACRE2Core/AcreSettings.cpp @@ -15,7 +15,7 @@ acre::Result CAcreSettings::save(std::string filename) { iniFile << "globalVolume = " << this->m_GlobalVolume << ";\n"; iniFile << "premixGlobalVolume = " << this->m_PremixGlobalVolume << ";\n"; iniFile << "disableUnmuteClients = " << (this->m_DisableUnmuteClients ? "true" : "false") << ";\n"; - iniFile << "disableTS3ChannelSwitch = " << (this->m_DisableTS3ChannelSwitch ? "true" : "false") << ";\n"; + iniFile << "disableChannelSwitch = " << (this->m_DisableChannelSwitch ? "true" : "false") << ";\n"; //LOG("Config Save: %f,%f", m_GlobalVolume, m_PremixGlobalVolume); iniFile.flush(); @@ -40,7 +40,7 @@ acre::Result CAcreSettings::load(std::string filename) { this->m_GlobalVolume = (float)config.GetReal("acre2", "globalVolume", 1.0f); this->m_PremixGlobalVolume = (float)config.GetReal("acre2", "premixGlobalVolume", 1.0f); this->m_DisableUnmuteClients = config.GetBoolean("acre2", "disableUnmuteClients", false); - this->m_DisableTS3ChannelSwitch = config.GetBoolean("acre2", "disableTS3ChannelSwitch", false); + this->m_DisableChannelSwitch = config.GetBoolean("acre2", "disableChannelSwitch", false); //LOG("Config Load: %f,%f", m_GlobalVolume, m_PremixGlobalVolume); this->m_Path = filename; @@ -65,7 +65,7 @@ CAcreSettings::CAcreSettings() : m_EnableAudioTest(false), m_DisableRadioFilter(false), m_DisableUnmuteClients(false), - m_DisableTS3ChannelSwitch(false), + m_DisableChannelSwitch(false), m_LastVersion(ACRE_VERSION), m_Path("acre2.ini") { diff --git a/extensions/src/ACRE2Core/AcreSettings.h b/extensions/src/ACRE2Core/AcreSettings.h index 02b4c61fe..6cc84c8b7 100644 --- a/extensions/src/ACRE2Core/AcreSettings.h +++ b/extensions/src/ACRE2Core/AcreSettings.h @@ -27,8 +27,8 @@ class CAcreSettings : DECLARE_MEMBER(bool, DisableMuting); DECLARE_MEMBER(bool, DisableRadioFilter); DECLARE_MEMBER(bool, DisableUnmuteClients); - DECLARE_MEMBER(bool, DisableTS3ChannelSwitch); + DECLARE_MEMBER(bool, DisableChannelSwitch); DECLARE_MEMBER(bool, EnableAudioTest); DECLARE_MEMBER(std::string, Path); diff --git a/extensions/src/ACRE2Core/Engine.cpp b/extensions/src/ACRE2Core/Engine.cpp index 34f168426..d8f329c75 100644 --- a/extensions/src/ACRE2Core/Engine.cpp +++ b/extensions/src/ACRE2Core/Engine.cpp @@ -28,7 +28,7 @@ #include "updateSelf.h" #include "setSelectableVoiceCurve.h" #include "setSetting.h" -#include "setTs3ChannelDetails.h" +#include "setChannelDetails.h" #include acre::Result CEngine::initialize(IClient *client, IServer *externalServer, std::string fromPipeName, std::string toPipeName) { @@ -83,7 +83,7 @@ acre::Result CEngine::initialize(IClient *client, IServer *externalServer, std:: this->getRpcEngine()->addProcedure(new updateSelf()); this->getRpcEngine()->addProcedure(new setSelectableVoiceCurve()); this->getRpcEngine()->addProcedure(new setSetting()); - this->getRpcEngine()->addProcedure(new setTs3ChannelDetails()); + this->getRpcEngine()->addProcedure(new setChannelDetails()); // Initialize the client, because it never was derp this->getClient()->initialize(); diff --git a/extensions/src/ACRE2Core/NamedPipeServer.cpp b/extensions/src/ACRE2Core/NamedPipeServer.cpp index a2c39c606..2da726fc6 100644 --- a/extensions/src/ACRE2Core/NamedPipeServer.cpp +++ b/extensions/src/ACRE2Core/NamedPipeServer.cpp @@ -224,7 +224,7 @@ acre::Result CNamedPipeServer::readLoop() { bool ret = ConnectNamedPipe(this->m_PipeHandleRead, NULL); if (GetLastError() == ERROR_PIPE_CONNECTED) { LOG("Client read connected"); - CEngine::getInstance()->getClient()->updateShouldSwitchTS3Channel(false); + CEngine::getInstance()->getClient()->updateShouldSwitchChannel(false); CEngine::getInstance()->getClient()->unMuteAll(); CEngine::getInstance()->getSoundEngine()->onClientGameConnected(); this->setConnectedRead(true); @@ -251,8 +251,8 @@ acre::Result CNamedPipeServer::readLoop() { } //Run channel switch to server channel - if (CEngine::getInstance()->getClient()->shouldSwitchTS3Channel()) { - CEngine::getInstance()->getClient()->moveToServerTS3Channel(); + if (CEngine::getInstance()->getClient()->shouldSwitchChannel()) { + CEngine::getInstance()->getClient()->moveToServerChannel(); } ret = false; @@ -289,7 +289,7 @@ acre::Result CNamedPipeServer::readLoop() { ret = DisconnectNamedPipe(this->m_PipeHandleRead); //Run channel switch to original channel - CEngine::getInstance()->getClient()->moveToPreviousTS3Channel(); + CEngine::getInstance()->getClient()->moveToPreviousChannel(); CEngine::getInstance()->getSoundEngine()->onClientGameDisconnected(); LOG("Client disconnected"); CEngine::getInstance()->getClient()->unMuteAll(); diff --git a/extensions/src/ACRE2Core/NamedPipeServer.h b/extensions/src/ACRE2Core/NamedPipeServer.h index 59c386268..65314ca91 100644 --- a/extensions/src/ACRE2Core/NamedPipeServer.h +++ b/extensions/src/ACRE2Core/NamedPipeServer.h @@ -22,7 +22,9 @@ class CNamedPipeServer : public IServer, public CLockable { acre::Result initialize( void ); acre::Result shutdown( void ); - acre::Result handleMessage(unsigned char *data) { (void) data; return acre::Result::notImplemented; } + acre::Result handleMessage(unsigned char* data) { (void)data; return acre::Result::notImplemented; } + acre::Result handleMessage(unsigned char* data, size_t length) { (void)data; return acre::Result::notImplemented; } + acre::Result sendMessage( IMessage *message ); diff --git a/extensions/src/ACRE2Core/setTs3ChannelDetails.h b/extensions/src/ACRE2Core/setChannelDetails.h similarity index 83% rename from extensions/src/ACRE2Core/setTs3ChannelDetails.h rename to extensions/src/ACRE2Core/setChannelDetails.h index 0b799d32f..b5532472e 100644 --- a/extensions/src/ACRE2Core/setTs3ChannelDetails.h +++ b/extensions/src/ACRE2Core/setChannelDetails.h @@ -7,14 +7,14 @@ #include -RPC_FUNCTION(setTs3ChannelDetails) { +RPC_FUNCTION(setChannelDetails) { const std::vector details = { std::string((char *)vMessage->getParameter(0)), std::string((char *)vMessage->getParameter(1)), std::string((char *)vMessage->getParameter(2)) }; - CEngine::getInstance()->getClient()->updateTs3ChannelDetails(details); + CEngine::getInstance()->getClient()->updateChannelDetails(details); return acre::Result::ok; } public: diff --git a/extensions/src/ACRE2Core/setSetting.h b/extensions/src/ACRE2Core/setSetting.h index 2466b25e6..998ae9ad0 100644 --- a/extensions/src/ACRE2Core/setSetting.h +++ b/extensions/src/ACRE2Core/setSetting.h @@ -21,9 +21,9 @@ RPC_FUNCTION(setSetting) { if (CAcreSettings::getInstance()->getDisableUnmuteClients() != (value != 1)) { CAcreSettings::getInstance()->setDisableUnmuteClients(value != 1); } - } else if (name == "disableTS3ChannelSwitch") { - if (CAcreSettings::getInstance()->getDisableTS3ChannelSwitch() != (value != 1)) { - CAcreSettings::getInstance()->setDisableTS3ChannelSwitch(value != 1); + } else if (name == "disableVoipChannelSwitch") { + if (CAcreSettings::getInstance()->getDisableChannelSwitch() != (value != 1)) { + CAcreSettings::getInstance()->setDisableChannelSwitch(value != 1); } } else { LOG("Setting [%s] failed to change to [%f]", name.c_str(), value); diff --git a/extensions/src/ACRE2DistortionTestPlugin/CommandServer.h b/extensions/src/ACRE2DistortionTestPlugin/CommandServer.h index 81fc76e67..31648b6c3 100644 --- a/extensions/src/ACRE2DistortionTestPlugin/CommandServer.h +++ b/extensions/src/ACRE2DistortionTestPlugin/CommandServer.h @@ -20,6 +20,12 @@ class CCommandServer : public IServer, public CLockable { acre::Result sendMessage(IMessage *msg); acre::Result handleMessage(unsigned char *msg); + acre::Result handleMessage(unsigned char* msg, size_t length) override { + (void)msg; + (void)length; + + return acre::Result::notImplemented; + } acre::Result release(void); diff --git a/extensions/src/ACRE2DistortionTestPlugin/TS3Client.cpp b/extensions/src/ACRE2DistortionTestPlugin/TS3Client.cpp index b5cdab362..4dfb55b45 100644 --- a/extensions/src/ACRE2DistortionTestPlugin/TS3Client.cpp +++ b/extensions/src/ACRE2DistortionTestPlugin/TS3Client.cpp @@ -1,20 +1,12 @@ -#include "compat.h" - -#include "TS3Client.h" +#include "AcreSettings.h" #include "Engine.h" -#include "Types.h" +#include "Log.h" +#include "Shlwapi.h" +#include "TS3Client.h" #include "TsFunctions.h" +#include "Types.h" +#include "compat.h" #include "shlobj.h" -#include "Shlwapi.h" -#include "Log.h" -#include -#include -#include -#include -#include -#include - -#include "AcreSettings.h" #pragma comment(lib, "Shlwapi.lib") @@ -23,10 +15,10 @@ extern TS3Functions ts3Functions; -//TS3Functions CTS3Client::ts3Functions; +// TS3Functions CTS3Client::ts3Functions; acre::Result CTS3Client::initialize(void) { - setPreviousTSChannel(INVALID_TS3_CHANNEL); + setPreviousChannel(INVALID_TS3_CHANNEL); return acre::Result::ok; } @@ -47,12 +39,10 @@ acre::Result CTS3Client::setMuted(const acre::id_t id_, const bool muted_) { } acre::Result CTS3Client::setMuted(std::list idList_, bool muted_) { - return acre::Result::ok; } acre::Result CTS3Client::getMuted(acre::id_t id_) { - return acre::Result::ok; } @@ -64,7 +54,6 @@ acre::Result CTS3Client::stop() { this->m_versionThreadHandle.join(); } this->setState(acre::State::stopped); - } return acre::Result::ok; } @@ -86,27 +75,26 @@ acre::Result CTS3Client::start(const acre::id_t id_) { return acre::Result::ok; } -acre::Result CTS3Client::exPersistVersion( void ) { - +acre::Result CTS3Client::exPersistVersion(void) { ts3Functions.setClientSelfVariableAsString(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_META_DATA, ACRE_VERSION_METADATA); ts3Functions.flushClientSelfUpdates(ts3Functions.getCurrentServerConnectionHandlerID(), NULL); ts3Functions.printMessageToCurrentTab("ACRE2 loaded and initialized"); ts3Functions.printMessageToCurrentTab(ACRE_VERSION_METADATA); - clock_t run = clock() / CLOCKS_PER_SEC; + clock_t run = clock() / CLOCKS_PER_SEC; clock_t delta = run; while (this->getState() == acre::State::running && CEngine::getInstance()->getExternalServer()) { - delta = (clock() / CLOCKS_PER_SEC) - run; - if (delta > (PERSIST_VERSION_TIMER / 1000) ) { + if (delta > (PERSIST_VERSION_TIMER / 1000)) { char selfVariableBuffer[4096]; if (CEngine::getInstance()->getGameServer()->getConnected()) { _snprintf_s(selfVariableBuffer, 4094, "%s\nArma Connected: Yes", ACRE_VERSION_METADATA); } else { _snprintf_s(selfVariableBuffer, 4094, "%s\nArma Connected: No", ACRE_VERSION_METADATA); } - ts3Functions.setClientSelfVariableAsString(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_META_DATA, selfVariableBuffer); + ts3Functions.setClientSelfVariableAsString( + ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_META_DATA, selfVariableBuffer); ts3Functions.flushClientSelfUpdates(ts3Functions.getCurrentServerConnectionHandlerID(), NULL); run = clock() / CLOCKS_PER_SEC; } @@ -120,7 +108,7 @@ acre::Result CTS3Client::exPersistVersion( void ) { bool CTS3Client::getVAD() { char *data; bool returnValue = false; - uint32_t res = ts3Functions.getPreProcessorConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), "vad", &data); + uint32_t res = ts3Functions.getPreProcessorConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), "vad", &data); if (!res) { if (!strcmp(data, "true")) { returnValue = true; @@ -157,7 +145,7 @@ acre::Result CTS3Client::localStartSpeaking(const acre::Speaking speakingType_, } else { stopDirectSpeaking = true; } - } else if (this->getVAD() && (this->getTsSpeakingState() == STATUS_TALKING)) { + } else if (this->getVAD() && (this->getSpeakingState() == STATUS_TALKING)) { stopDirectSpeaking = true; } } @@ -202,7 +190,7 @@ acre::Result CTS3Client::localStopSpeaking(const acre::Speaking speakingType_) { } } else { this->setOnRadio(false); - if (this->getTsSpeakingState() == STATUS_TALKING) { + if (this->getSpeakingState() == STATUS_TALKING) { resendDirectSpeaking = true; } } @@ -213,8 +201,8 @@ acre::Result CTS3Client::localStopSpeaking(const acre::Speaking speakingType_) { } else { resendDirectSpeaking = true; } - } else if (this->getTsSpeakingState() == STATUS_TALKING) { - resendDirectSpeaking = true; + } else if (this->getSpeakingState() == STATUS_TALKING) { + resendDirectSpeaking = true; } } @@ -232,9 +220,10 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { if (currentStatus != status_) { uint32_t res = 0u; if (status_) { - res = ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_NONE); + res = ts3Functions.setClientSelfVariableAsInt( + ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_NONE); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error toggling microphone enabled: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -242,9 +231,10 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { return acre::Result::ok; } } else { - res = ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_MUTED); + res = ts3Functions.setClientSelfVariableAsInt( + ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_MUTED); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error, failed to disable microphone input: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -254,7 +244,7 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { } res = ts3Functions.flushClientSelfUpdates(ts3Functions.getCurrentServerConnectionHandlerID(), NULL); if (!((res == ERROR_ok) || (res == ERROR_ok_no_update))) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("STOP TALKING: Error flushing after toggling microphone muted: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -266,11 +256,11 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { } bool CTS3Client::getInputStatus() { - bool status = false; - int32_t ret = 0u; + bool status = false; + int32_t ret = 0u; uint32_t res = ts3Functions.getClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, &ret); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error querying microphone input status: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -286,16 +276,16 @@ bool CTS3Client::getInputStatus() { } acre::Result CTS3Client::playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_) { - if (!PathFileExistsA(path_.c_str())) { return acre::Result::error; } char soundpackDb[32]; uint32_t ret = 0u; - uint32_t res = ts3Functions.getClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_OUTPUT_MUTED, (int32_t *) &ret); + uint32_t res = + ts3Functions.getClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_OUTPUT_MUTED, (int32_t *) &ret); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error checking playback status: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -308,31 +298,25 @@ acre::Result CTS3Client::playSound(std::string path_, acre::vec3_fp32_t position } // create a volume ranged from -40 to 0dB change - _snprintf_s(soundpackDb, 32, "%f", (-40.0f + (40.0f * volume_) ) ); + _snprintf_s(soundpackDb, 32, "%f", (-40.0f + (40.0f * volume_))); // change the soundpack volume for this squawks volume - ts3Functions.setPlaybackConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), - "volume_factor_wave", - soundpackDb); + ts3Functions.setPlaybackConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), "volume_factor_wave", soundpackDb); TS3_VECTOR vector = {position_.x, position_.z, position_.y}; TRACE("HIT [%f,%f,%f]", vector.x, vector.z, vector.y); uint64_t playHandle; - ret = ts3Functions.playWaveFileHandle(ts3Functions.getCurrentServerConnectionHandlerID(), - path_.c_str(), - looping_, - &playHandle); - ret = ts3Functions.set3DWaveAttributes(ts3Functions.getCurrentServerConnectionHandlerID(), - playHandle, - &vector); + ret = ts3Functions.playWaveFileHandle(ts3Functions.getCurrentServerConnectionHandlerID(), path_.c_str(), looping_, &playHandle); + ret = ts3Functions.set3DWaveAttributes(ts3Functions.getCurrentServerConnectionHandlerID(), playHandle, &vector); return acre::Result::ok; } -std::string CTS3Client::getUniqueId( ) { +std::string CTS3Client::getUniqueId() { char *uniqueId; std::string serverUniqueId = ""; - uint32_t res = ts3Functions.getServerVariableAsString(ts3Functions.getCurrentServerConnectionHandlerID(), VIRTUALSERVER_UNIQUE_IDENTIFIER, &uniqueId); + uint32_t res = ts3Functions.getServerVariableAsString( + ts3Functions.getCurrentServerConnectionHandlerID(), VIRTUALSERVER_UNIQUE_IDENTIFIER, &uniqueId); if (res == ERROR_ok) { serverUniqueId = std::string(uniqueId); if (uniqueId) { @@ -358,7 +342,7 @@ std::string CTS3Client::getConfigFilePath(void) { return tempFolder; } -std::string CTS3Client::getTempFilePath( void ) { +std::string CTS3Client::getTempFilePath(void) { char tempPath[MAX_PATH - 14]; GetTempPathA(sizeof(tempPath), tempPath); std::string tempFolder = std::string(tempPath); @@ -381,9 +365,10 @@ acre::Result CTS3Client::microphoneOpen(bool status_) { this->setInputActive(false); } - uint32_t res = ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_DEACTIVATED, micStatus); + uint32_t res = + ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_DEACTIVATED, micStatus); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("STOP TALKING: Error toggling push-to-talk: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -393,7 +378,7 @@ acre::Result CTS3Client::microphoneOpen(bool status_) { res = ts3Functions.flushClientSelfUpdates(ts3Functions.getCurrentServerConnectionHandlerID(), NULL); if (!(res == ERROR_ok || res == ERROR_ok_no_update)) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("STOP TALKING: Error flushing after toggling push-to-talk: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -403,67 +388,67 @@ acre::Result CTS3Client::microphoneOpen(bool status_) { return acre::Result::ok; } -acre::Result CTS3Client::unMuteAll( void ) { +acre::Result CTS3Client::unMuteAll(void) { anyID clientId; anyID *clientList; - uint32_t total_retries = 0; + uint32_t total_retries = 0; uint32_t total_intentional_runs = 0; - //for (total_intentional_runs = 0; total_intentional_runs < 3; total_intentional_runs++) { - uint32_t res = ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId); - if (res == ERROR_ok) { - - res = ERROR_undefined; - for (total_retries = 0; (total_retries < 5) && (res != ERROR_ok); total_retries++) { - res = ts3Functions.getClientList(ts3Functions.getCurrentServerConnectionHandlerID(), &clientList); - if (res == ERROR_ok) { - res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); - //if (res != ERROR_ok) { - // Sleep(500 * total_retries); - //} - ts3Functions.freeMemory(clientList); - } - } - - /* - /* - This was the alternative method originally, but it was hitting the spam threshold */ - /* Disable this method + // for (total_intentional_runs = 0; total_intentional_runs < 3; total_intentional_runs++) { + uint32_t res = ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId); + if (res == ERROR_ok) { + res = ERROR_undefined; + for (total_retries = 0; (total_retries < 5) && (res != ERROR_ok); total_retries++) { res = ts3Functions.getClientList(ts3Functions.getCurrentServerConnectionHandlerID(), &clientList); if (res == ERROR_ok) { - for (x=0;clientList[x]!=0 && total_retries < 20;x++) { - anyID tempList[2]; - uint32_t tries_on_client; - - tempList[0] = (anyID)clientList[x]; - tempList[1] = 0x0000; - - res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); - for (tries_on_client = 0; tries_on_client < 5 && total_retries < 20 && res != ERROR_ok; tries_on_client++, total_retries++) { - res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), tempList, NULL); - if (res != ERROR_ok) { - Sleep(500 * tries_on_client); - } - } - } + res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); + // if (res != ERROR_ok) { + // Sleep(500 * total_retries); + //} ts3Functions.freeMemory(clientList); } - */ } + + /* + /* - This was the alternative method originally, but it was hitting the spam threshold */ + /* Disable this method + res = ts3Functions.getClientList(ts3Functions.getCurrentServerConnectionHandlerID(), &clientList); + if (res == ERROR_ok) { + for (x=0;clientList[x]!=0 && total_retries < 20;x++) { + anyID tempList[2]; + uint32_t tries_on_client; + + tempList[0] = (anyID)clientList[x]; + tempList[1] = 0x0000; + + res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); + for (tries_on_client = 0; tries_on_client < 5 && total_retries < 20 && res != ERROR_ok; tries_on_client++, total_retries++) + { res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), tempList, NULL); if (res != ERROR_ok) + { Sleep(500 * tries_on_client); + } + } + } + ts3Functions.freeMemory(clientList); + } + */ + } // Sleep(500); //} return acre::Result::ok; } -acre::Result CTS3Client::moveToServerTS3Channel() { - if (!CAcreSettings::getInstance()->getDisableTS3ChannelSwitch()) { +acre::Result CTS3Client::moveToServerChannel() { + if (!CAcreSettings::getInstance()->getDisableChannelSwitch()) { anyID clientId; - std::vector details = getTs3ChannelDetails(); + std::vector details = getChannelDetails(); if (ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId) == ERROR_ok) { uint64_t currentChannelId = INVALID_TS3_CHANNEL; - if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == ERROR_ok && getPreviousTSChannel() == INVALID_TS3_CHANNEL) { - setPreviousTSChannel(currentChannelId); + if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == + ERROR_ok && + getPreviousChannel() == INVALID_TS3_CHANNEL) { + setPreviousChannel(currentChannelId); } const uint64_t channelId = findChannelByNames(details); @@ -472,27 +457,29 @@ acre::Result CTS3Client::moveToServerTS3Channel() { if (details.at(1) != "" && details.at(0) != "") { password = details.at(1); } - ts3Functions.requestClientMove(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, channelId, password.c_str(), nullptr); + ts3Functions.requestClientMove( + ts3Functions.getCurrentServerConnectionHandlerID(), clientId, channelId, password.c_str(), nullptr); } } } - setShouldSwitchTS3Channel(false); + setShouldSwitchChannel(false); return acre::Result::ok; } -acre::Result CTS3Client::moveToPreviousTS3Channel() { - if (!CAcreSettings::getInstance()->getDisableTS3ChannelSwitch()) { +acre::Result CTS3Client::moveToPreviousChannel() { + if (!CAcreSettings::getInstance()->getDisableChannelSwitch()) { anyID clientId; if (ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId) == ERROR_ok) { uint64_t currentChannelId = INVALID_TS3_CHANNEL; - if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == ERROR_ok) { - const uint64_t channelId = getPreviousTSChannel(); + if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == + ERROR_ok) { + const uint64_t channelId = getPreviousChannel(); if (channelId != INVALID_TS3_CHANNEL && channelId != currentChannelId) { ts3Functions.requestClientMove(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, channelId, "", nullptr); } } } - setPreviousTSChannel(INVALID_TS3_CHANNEL); + setPreviousChannel(INVALID_TS3_CHANNEL); } return acre::Result::ok; } @@ -500,7 +487,7 @@ acre::Result CTS3Client::moveToPreviousTS3Channel() { uint64_t CTS3Client::findChannelByNames(std::vector details_) { uint64_t *channelList; if (ts3Functions.getChannelList(ts3Functions.getCurrentServerConnectionHandlerID(), &channelList) == ERROR_ok) { - uint64_t channelId = INVALID_TS3_CHANNEL; + uint64_t channelId = INVALID_TS3_CHANNEL; uint64_t defaultChannelId = INVALID_TS3_CHANNEL; std::map channelMap; std::string name = details_.at(2); @@ -510,8 +497,9 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { while (*channelList) { channelId = *channelList; channelList++; - char* channelName; - if (ts3Functions.getChannelVariableAsString(ts3Functions.getCurrentServerConnectionHandlerID(), channelId, CHANNEL_NAME, &channelName) == ERROR_ok) { + char *channelName; + if (ts3Functions.getChannelVariableAsString( + ts3Functions.getCurrentServerConnectionHandlerID(), channelId, CHANNEL_NAME, &channelName) == ERROR_ok) { std::string channelNameString = std::string(channelName); if (channelNameString.find(DEFAULT_TS3_CHANNEL) != -1 || (details_.at(0) != "" && channelNameString == name)) { if (channelNameString == DEFAULT_TS3_CHANNEL) { @@ -523,9 +511,9 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { } uint64_t bestChannelId = INVALID_TS3_CHANNEL; - int32_t bestMatches = 0; - int32_t bestDistance = 10; - for (auto& element : channelMap) { + int32_t bestMatches = 0; + int32_t bestDistance = 10; + for (auto &element : channelMap) { std::string fullChannelName = element.second; // Full comparison if (fullChannelName.compare(name) == 0) { @@ -536,20 +524,20 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { // Word comparison const int32_t matches = getWordMatches(cleanChannelName, name); if (matches > bestMatches) { - bestMatches = matches; + bestMatches = matches; bestChannelId = element.first; continue; } // Char comparison const int32_t distance = levenshteinDistance(cleanChannelName, name); if (distance <= bestDistance) { - bestDistance = distance; + bestDistance = distance; bestChannelId = element.first; } } if (bestChannelId == INVALID_TS3_CHANNEL) { - if (details_.at(0) != "") { - details_.at(0) = ""; + if (!details_.at(0).empty()) { + details_.at(0).clear(); bestChannelId = findChannelByNames(details_); } else if (defaultChannelId != INVALID_TS3_CHANNEL) { bestChannelId = defaultChannelId; @@ -560,79 +548,19 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { return INVALID_TS3_CHANNEL; } -unsigned int CTS3Client::getWordMatches(const std::string& string1_, const std::string& string2_) { - std::vector words1, words2; - std::string temp; - std::stringstream stringstream1(string1_); - while (stringstream1 >> temp) { - words1.push_back(temp); - } - std::stringstream stringstream2(string2_); - while (stringstream2 >> temp) { - words2.push_back(temp); - } - - int32_t matches = 0; - for (auto& word1 : words1) { - for (auto& word2 : words2) { - if (word1 == word2) { - matches++; - } - } - } - return matches; -} - -uint32_t CTS3Client::levenshteinDistance(const std::string& string1_, const std::string& string2_) { - int32_t length1 = string1_.size(); - const int32_t length2 = string2_.size(); - - const decltype(length1) columnStart = decltype(length1)(1); - - decltype(length1)*const column = new decltype(length1)[length1 + 1]; - std::iota(column + columnStart, column + length1 + 1, columnStart); - - for (auto x = columnStart; x <= length2; x++) { - column[0] = x; - int32_t lastDiagonal = x - columnStart; - for (auto y = columnStart; y <= length1; y++) { - const int32_t oldDiagonal = column[y]; - const std::initializer_list possibilities = { - column[y] + 1, - column[y - 1] + 1, - lastDiagonal + (string1_[y - 1] == string2_[x - 1] ? 0 : 1) - }; - column[y] = min(possibilities); - lastDiagonal = oldDiagonal; - } - } - const int32_t result = column[length1]; - delete[] column; - return result; -} - -std::string CTS3Client::removeSubstrings(std::string string_, std::string substring_) { - const std::string::size_type substringLength = substring_.length(); - for (auto iterator = string_.find(substring_); - iterator != std::string::npos; - iterator = string_.find(substring_)) - string_.erase(iterator, substringLength); - return string_; -} - -acre::Result CTS3Client::updateTs3ChannelDetails(std::vector details_) { - setTs3ChannelDetails(details_); +acre::Result CTS3Client::updateChannelDetails(std::vector details_) { + setChannelDetails(details_); if (!details_.empty()) { - updateShouldSwitchTS3Channel(true); + updateShouldSwitchChannel(true); } return acre::Result::ok; } -acre::Result CTS3Client::updateShouldSwitchTS3Channel(const bool state_) { - setShouldSwitchTS3Channel(state_); +acre::Result CTS3Client::updateShouldSwitchChannel(const bool state_) { + setShouldSwitchChannel(state_); return acre::Result::ok; } -bool CTS3Client::shouldSwitchTS3Channel() { - return getShouldSwitchTS3Channel(); +bool CTS3Client::shouldSwitchChannel() { + return getShouldSwitchChannel(); } diff --git a/extensions/src/ACRE2DistortionTestPlugin/TS3Client.h b/extensions/src/ACRE2DistortionTestPlugin/TS3Client.h index c273d10c5..5b2d76f3d 100644 --- a/extensions/src/ACRE2DistortionTestPlugin/TS3Client.h +++ b/extensions/src/ACRE2DistortionTestPlugin/TS3Client.h @@ -11,25 +11,29 @@ class CTS3Client : public IClient { //static TS3Functions ts3Functions; - CTS3Client() {}; - ~CTS3Client() {}; + CTS3Client() = default; + ~CTS3Client() final = default; - acre::Result initialize(void); + acre::Result initialize(void) final; - acre::Result setMuted(const acre::id_t id_, const bool muted_); - acre::Result setMuted(std::list idList_, const bool muted_); + acre::Result setMuted(const acre::id_t id_, const bool muted_) final; + acre::Result setMuted(std::list idList_, const bool muted_) final; - acre::Result getMuted(const acre::id_t id_); + acre::Result getMuted(const acre::id_t id_) final; - acre::Result stop(); - acre::Result start(const acre::id_t id_); + acre::Result stop() final; + acre::Result start(const acre::id_t id_) final; acre::Result exPersistVersion(void); - acre::Result enableMicrophone(const bool status_); + acre::Result setClientMetadata(const char* const data); + + acre::Result enableMicrophone(const bool status_) final; bool getInputStatus(); + bool getVAD(); + /*! * \brief Handles local player starting speaking. * @@ -37,7 +41,7 @@ class CTS3Client : public IClient { * * \return acre::Result::ok if operation successful */ - acre::Result localStartSpeaking(const acre::Speaking speakingType_); + acre::Result localStartSpeaking(const acre::Speaking speakingType_) final; /*! * \brief Handles local player starting speaking. @@ -47,59 +51,37 @@ class CTS3Client : public IClient { * * \return acre::Result::ok if operation successful */ - acre::Result localStartSpeaking(const acre::Speaking speakingType_, std::string radioId_); + acre::Result localStartSpeaking(const acre::Speaking speakingType_, std::string radioId_) final; /*! - * \brief Handles local player stopping speaking. - * - * \param[in] speakingType_ ACRE speaking type - * - * \return acre::Result::ok if operation successful - */ - acre::Result localStopSpeaking(const acre::Speaking speakingType_); + * \brief Handles local player stopping speaking. + * + * \param[in] speakingType_ ACRE speaking type + * + * \return acre::Result::ok if operation successful + */ + acre::Result localStopSpeaking(const acre::Speaking speakingType_) final; - std::string getTempFilePath(void); - std::string getConfigFilePath(void); + std::string getTempFilePath(void) final; + std::string getConfigFilePath(void) final; - acre::Result playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_); + acre::Result playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_) final; - std::string getUniqueId(); + std::string getUniqueId() final; - bool getVAD(); + acre::Result microphoneOpen(const bool status_) final; + + acre::Result unMuteAll(void) final; + + acre::Result moveToServerChannel() final; + acre::Result moveToPreviousChannel() final; + uint64 findChannelByNames(std::vector details_) final; + + acre::Result updateChannelDetails(std::vector details_) final; + acre::Result updateShouldSwitchChannel(const bool state_) final; + bool shouldSwitchChannel() final; - acre::Result microphoneOpen(const bool status_); - - acre::Result unMuteAll(void); - - acre::Result moveToServerTS3Channel(); - acre::Result moveToPreviousTS3Channel(); - uint64 findChannelByNames(std::vector details_); - uint32_t getWordMatches(const std::string& string1_, const std::string& string2_); - uint32_t levenshteinDistance(const std::string& string1_, const std::string& string2_); - std::string removeSubstrings(std::string string_, std::string substring_); - acre::Result updateTs3ChannelDetails(std::vector details_); - acre::Result updateShouldSwitchTS3Channel(const bool state_); - bool shouldSwitchTS3Channel(); - - inline void setState(acre::State value) final { m_state = value; } - inline acre::State getState() const final { return m_state; } - - DECLARE_MEMBER(bool, hadVAD); - DECLARE_MEMBER(bool, InputActive); - DECLARE_MEMBER(bool, OnRadio); - DECLARE_MEMBER(int32_t, TsSpeakingState); - DECLARE_MEMBER(bool, RadioPTTDown); - DECLARE_MEMBER(bool, IntercomPTTDown); - DECLARE_MEMBER(bool, MainPTTDown); - DECLARE_MEMBER(bool, DirectFirst); - DECLARE_MEMBER(bool, HitTSSpeakingEvent); - DECLARE_MEMBER(bool, IsX3DInitialized); - DECLARE_MEMBER(uint32_t, SpeakerMask); - DECLARE_MEMBER(uint64, PreviousTSChannel); - DECLARE_MEMBER(std::vector, Ts3ChannelDetails); - DECLARE_MEMBER(bool, ShouldSwitchTS3Channel) -protected: +private: std::thread m_versionThreadHandle; - char *m_vadLevel; - acre::State m_state; + char* m_vadLevel = nullptr; }; diff --git a/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_channelEvents.cpp b/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_channelEvents.cpp index efa8f194d..a0363ec8f 100644 --- a/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_channelEvents.cpp +++ b/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_channelEvents.cpp @@ -82,5 +82,5 @@ void ts3plugin_onClientMoveMovedEvent(uint64 serverConnectionHandlerID, anyID cl } void ts3plugin_onUpdateChannelEditedEvent(uint64 serverConnectionHandlerID, uint64 channelID, anyID invokerID, const char* invokerName, const char* invokerUniqueIdentifier) { - CEngine::getInstance()->getClient()->updateShouldSwitchTS3Channel(true); + CEngine::getInstance()->getClient()->updateShouldSwitchChannel(true); } diff --git a/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_speaking.cpp b/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_speaking.cpp index 66e324193..f76f69ad3 100644 --- a/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_speaking.cpp +++ b/extensions/src/ACRE2DistortionTestPlugin/TsCallbacks_speaking.cpp @@ -32,7 +32,7 @@ void ts3plugin_onTalkStatusChangeEvent(uint64 serverConnectionHandlerID, int sta return; } - ((CTS3Client *) (CEngine::getInstance()->getClient()))->setTsSpeakingState(status); + ((CTS3Client *) (CEngine::getInstance()->getClient()))->setSpeakingState(status); if (CEngine::getInstance()->getSoundSystemOverride()) { return; } else if (((CTS3Client *) (CEngine::getInstance()->getClient()))->getOnRadio()) { diff --git a/extensions/src/ACRE2Mumble/CMakeLists.txt b/extensions/src/ACRE2Mumble/CMakeLists.txt new file mode 100644 index 000000000..26a5ab283 --- /dev/null +++ b/extensions/src/ACRE2Mumble/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_minimum_required (VERSION 3.10) + +set(ACRE_NAME "ACRE2Mumble") + +acre_set_build_output() +acre_set_linker_options() + +enable_language(ASM_MASM) +file(GLOB_RECURSE SOURCES *.h *.hpp *.c *.cpp *.asm mumble_includes/*) + +include_directories(mumble_includes) + +add_library( ${ACRE_NAME} MODULE ${SOURCES} ${GLOBAL_SOURCES}) +target_link_libraries(${ACRE_NAME} ACRE2Core ACRE2Shared x3daudio) +set_target_properties(${ACRE_NAME} PROPERTIES FOLDER ACRE2 LINK_FLAGS -SAFESEH:NO) +target_compile_features(${ACRE_NAME} PRIVATE cxx_std_17) + + +# Copy and rename +if(USE_64BIT_BUILD) + set(FINAL_DLL_NAME acre2_win64.dll) +else() + set(FINAL_DLL_NAME acre2_win32.dll) +endif() + +add_custom_command(TARGET ${ACRE_NAME} POST_BUILD + # Copy DLL to plugins + COMMAND ${CMAKE_COMMAND} -E copy $ ${PROJECT_SOURCE_DIR}/../plugin/mumble/${FINAL_DLL_NAME} + # Copy PDB to symbols + COMMAND ${CMAKE_COMMAND} -E copy $/${ACRE_NAME}.pdb ${PROJECT_SOURCE_DIR}/../symbols/${ACRE_ARCH}/${ACRE_NAME}.pdb +) diff --git a/extensions/src/ACRE2Mumble/MumbleCallbacks_channelEvents.cpp b/extensions/src/ACRE2Mumble/MumbleCallbacks_channelEvents.cpp new file mode 100644 index 000000000..46e2d2721 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCallbacks_channelEvents.cpp @@ -0,0 +1,16 @@ +#include "AcreSettings.h" +#include "Engine.h" +#include "Log.h" +#include "MumbleFunctions.h" +#include "Types.h" +#include "compat.h" + +extern MumbleAPI_v_1_0_x mumAPI; +extern mumble_connection_t activeConnection; +extern mumble_plugin_id_t pluginID; + +void mumble_onChannelRenamed(mumble_connection_t connection, mumble_channelid_t channelID) { + (void) connection; + (void) channelID; + CEngine::getInstance()->getClient()->updateShouldSwitchChannel(true); +} diff --git a/extensions/src/ACRE2Mumble/MumbleCallbacks_init.cpp b/extensions/src/ACRE2Mumble/MumbleCallbacks_init.cpp new file mode 100644 index 000000000..80ea6abf2 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCallbacks_init.cpp @@ -0,0 +1,95 @@ +#include "Engine.h" +#include "Log.h" +#include "MumbleClient.h" +#include "MumbleCommandServer.h" +#include "MumbleFunctions.h" +#include "MumbleEventLoop.h" +#include "compat.h" +#include "helpers.h" + +#define FROM_PIPENAME "\\\\.\\pipe\\acre_comm_pipe_fromTS" +#define TO_PIPENAME "\\\\.\\pipe\\acre_comm_pipe_toTS" + +extern MumbleAPI_v_1_0_x mumAPI; +mumble_connection_t activeConnection = -1; +mumble_plugin_id_t pluginID = -1; + +uint32_t mumble_getFeatures() { + return MUMBLE_FEATURE_AUDIO; +} + +void mumble_registerAPIFunctions(void *apiStruct) { + mumAPI = MUMBLE_API_CAST(apiStruct); +} + +mumble_error_t mumble_init(mumble_plugin_id_t id) { + pluginID = id; + + acre::MumbleEventLoop::getInstance().start(); + + if (mumAPI.getActiveServerConnection(pluginID, &activeConnection) != MUMBLE_STATUS_OK) { + activeConnection = -1; + } + + CEngine::getInstance()->initialize(new CMumbleClient(), new CMumbleCommandServer(), FROM_PIPENAME, TO_PIPENAME); + if (CEngine::getInstance() != NULL) { + if (((CMumbleCommandServer *) CEngine::getInstance()->getExternalServer()) != NULL) { + ((CMumbleCommandServer *) CEngine::getInstance()->getExternalServer())->setCommandId(pluginID); + } + if (pluginID != -1) + ((CMumbleCommandServer *) CEngine::getInstance()->getExternalServer())->setCommandId(pluginID); + if (activeConnection != -1) { + // we are activating while connected, call it + // virtualize a connect event + mumble_onServerSynchronized(activeConnection); + } + } + + return MUMBLE_STATUS_OK; +} + +void mumble_onServerSynchronized(mumble_connection_t connection) { + acre::MumbleEventLoop::getInstance().queue([connection]() { + activeConnection = connection; + + // set ID on every new connection + acre::id_t clientId = 0; + mumAPI.getLocalUserID(pluginID, activeConnection, (mumble_userid_t*)&clientId); + CEngine::getInstance()->getSelf()->setId(clientId); + CEngine::getInstance()->getExternalServer()->setId(clientId); + + // subscribe to all channels to receive event + if (CEngine::getInstance()->getClient()->getState() != acre::State::running) { + CEngine::getInstance()->getClient()->start(static_cast(clientId)); + } + }); +} + +void mumble_onServerDisconnected(mumble_connection_t connection) { + acre::MumbleEventLoop::getInstance().queue([connection]() { + activeConnection = -1; + + if ((CEngine::getInstance()->getClient()->getState() != acre::State::stopped) && + (CEngine::getInstance()->getClient()->getState() != acre::State::stopping)) { + CEngine::getInstance()->getClient()->stop(); + } + }); +} + +void mumble_shutdown() { + acre::MumbleEventLoop::getInstance().queue([]() { + if ((CEngine::getInstance()->getClient()->getState() != acre::State::stopped) && + (CEngine::getInstance()->getClient()->getState() != acre::State::stopping)) { + CEngine::getInstance()->getClient()->stop(); + } + CEngine::getInstance()->stop(); + }); + + acre::MumbleEventLoop::getInstance().stop(); +} + +void mumble_releaseResource(const void *) { + // Nothing to do here since we never pass allocated resourced to Mumble + // that needed freeing + LOG("ERROR: Unexpectedly called mumble_releaseResource"); +} diff --git a/extensions/src/ACRE2Mumble/MumbleCallbacks_pluginEvents.cpp b/extensions/src/ACRE2Mumble/MumbleCallbacks_pluginEvents.cpp new file mode 100644 index 000000000..035c16b87 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCallbacks_pluginEvents.cpp @@ -0,0 +1,20 @@ +#include "Engine.h" +#include "Log.h" +#include "MumbleFunctions.h" +#include "MumbleEventLoop.h" +#include "compat.h" + +// +// Handle a command event +// +bool mumble_onReceiveData(mumble_connection_t connection, mumble_userid_t sender, const uint8_t *data, size_t dataLength, const char *dataID) { + if ((dataLength > 0U) && CEngine::getInstance()->getExternalServer()) { + // Copy data to make sure it stays available in the async processing + std::string dataString(reinterpret_cast(data), dataLength); + acre::MumbleEventLoop::getInstance().queue([dataString]() { + CEngine::getInstance()->getExternalServer()->handleMessage((unsigned char *)dataString.data(), dataString.size()); + }); + return true; + } + return false; +} diff --git a/extensions/src/ACRE2Mumble/MumbleCallbacks_sound.cpp b/extensions/src/ACRE2Mumble/MumbleCallbacks_sound.cpp new file mode 100644 index 000000000..6022f882e --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCallbacks_sound.cpp @@ -0,0 +1,153 @@ +#include "Engine.h" +#include "FilterPosition.h" +#include "FilterVolume.h" +#include "Log.h" +#include "MumbleClient.h" +#include "Wave.h" +#include "compat.h" + +#include +#define _USE_MATH_DEFINES + +#include "AcreDsp.h" + +#include +#include + +using LIMITER = std::numeric_limits; + +bool mumble_onAudioSourceFetched(float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate, bool isSpeech, mumble_userid_t userID) { + (void) sampleRate; + + if (CEngine::getInstance()->getSoundSystemOverride()) { + return false; + } + + if (!CEngine::getInstance()->getGameServer()) { + return false; + } + + if (!CEngine::getInstance()->getGameServer()->getConnected()) { + return false; + } + + // Make this faster + const std::uint32_t mixdownSampleLength = sampleCount; + int16_t *mixdownSamples = new (std::nothrow) int16_t[mixdownSampleLength]; + if (mixdownSamples == nullptr) { + return false; + } + + if (channelCount > 1) { + std::uint32_t c = 0; + for (std::uint32_t x = 0; x < sampleCount * channelCount; x += channelCount) { + float sample = 0.0F; + for (int i = 0; i < channelCount; i++) { + sample += outputPCM[x + i]; + } + sample = sample / static_cast(channelCount); + if (sample > 1.0F) { + sample = 1.0F; + } else if (sample < -1.0F) { + sample = -1.0F; + } + mixdownSamples[c] = static_cast(sample * LIMITER::max()); + c++; // lulz + } + } else { + for (std::uint32_t c = 0; c < mixdownSampleLength; ++c) { + float sample = outputPCM[c]; + if (sample > 1.0F) { + sample = 1.0F; + } else if (sample < -1.0F) { + sample = -1.0F; + } + mixdownSamples[c] = static_cast(sample * LIMITER::max()); + } + } + + // should always be channel count of 1 since everything is mono and mumble is ... using stereo + // dumb legacy crap. + CEngine::getInstance()->getSoundEngine()->onEditPlaybackVoiceDataEvent( + static_cast(userID), mixdownSamples, sampleCount, 1); + + + if (channelCount > 1) { + std::uint32_t c = 0; + for (std::uint32_t x = 0; x < sampleCount * channelCount; x += channelCount) { + float mixedSample = 0.0F; + if (mixdownSamples[c] > 0) { + mixedSample = static_cast(mixdownSamples[c]) / LIMITER::max(); + } else { + mixedSample = -static_cast(mixdownSamples[c]) / LIMITER::min(); + } + + mixedSample = mixedSample / static_cast(channelCount); + for (int i = 0; i < channelCount; i++) { + outputPCM[x + i] = mixedSample; + } + c++; + } + } else { + for (std::uint32_t c = 0; c < mixdownSampleLength; ++c) { + float mixedSample = 0.0F; + if (mixdownSamples[c] > 0) { + mixedSample = static_cast(mixdownSamples[c]) / LIMITER::max(); + } else { + mixedSample = -static_cast(mixdownSamples[c]) / LIMITER::min(); + } + outputPCM[c] = mixedSample; + } + } + delete[] mixdownSamples; + return true; +} + +bool mumble_onAudioOutputAboutToPlay(float *outputPCM, uint32_t sampleCount, uint16_t channelCount, uint32_t sampleRate) { + (void) sampleRate; + + if (CEngine::getInstance()->getSoundSystemOverride()) { + return false; + } + + if (!CEngine::getInstance()->getGameServer()) { + return false; + } + + if (!CEngine::getInstance()->getGameServer()->getConnected()) { + return false; + } + + uint32_t speakerMask = SPEAKER_STEREO; + + // Make this faster + const uint32_t mixdownSampleLength = sampleCount * channelCount; + int16_t *mixdownSamples = new (std::nothrow) int16_t[mixdownSampleLength]; + if (mixdownSamples == nullptr) { + return false; + } + + for (uint32_t c = 0; c <= mixdownSampleLength - 1U; ++c) { + float sample = outputPCM[c]; + if (sample > 1.0F) { + sample = 1.0F; + } else if (sample < -1.0F) { + sample = -1.0F; + } + mixdownSamples[c] = static_cast(sample * LIMITER::max()); + } + + CEngine::getInstance()->getSoundEngine()->onEditMixedPlaybackVoiceDataEvent(mixdownSamples, sampleCount, channelCount, speakerMask); + + for (uint32_t c = 0; c <= mixdownSampleLength - 1; ++c) { + float mixedSample = 0.0F; + if (mixdownSamples[c] > 0) { + mixedSample = static_cast(mixdownSamples[c]) / LIMITER::max(); + } else { + mixedSample = -static_cast(mixdownSamples[c]) / LIMITER::min(); + } + outputPCM[c] = mixedSample; + } + delete[] mixdownSamples; + return true; +} diff --git a/extensions/src/ACRE2Mumble/MumbleCallbacks_speaking.cpp b/extensions/src/ACRE2Mumble/MumbleCallbacks_speaking.cpp new file mode 100644 index 000000000..2fe03c5b2 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCallbacks_speaking.cpp @@ -0,0 +1,82 @@ +#include "Engine.h" +#include "Log.h" +#include "Macros.h" +#include "MumbleClient.h" +#include "MumbleEventLoop.h" +#include "Types.h" +#include "compat.h" + +// +// Mumble Speaking callbacks +// +void mumble_onUserTalkingStateChanged(mumble_connection_t connection, mumble_userid_t userID, mumble_talking_state_t status) { + acre::MumbleEventLoop::getInstance().queue([connection, userID, status]() { + TRACE("mumble_onUserTalkingStateChanged ENTER: %d", status); + if (static_cast(userID) != CEngine::getInstance()->getSelf()->getId()) { + return; + } + + if (CEngine::getInstance()->getClient()->getState() != acre::State::running) { + return; + } + + if (!CEngine::getInstance()->getGameServer()) { + return; + } + + if (CEngine::getInstance()->getState() != acre::State::running) { + return; + } + + if (!CEngine::getInstance()->getGameServer()->getConnected()) { + return; + } + + CEngine::getInstance()->getClient()->setSpeakingState(status); + if (CEngine::getInstance()->getSoundSystemOverride()) { + return; + } + + if (CEngine::getInstance()->getClient()->getOnRadio()) { + if (CEngine::getInstance()->getClient()->getVAD()) { + return; + } + + if (status == MUMBLE_TS_PASSIVE || status == MUMBLE_TS_INVALID) { + if ((!CEngine::getInstance()->getClient()->getRadioPTTDown()) + && (!CEngine::getInstance()->getClient()->getGodPTTDown()) + && (!CEngine::getInstance()->getClient()->getZeusPTTDown())) { + CEngine::getInstance()->getClient()->setOnRadio(false); + } + else { + if (!CEngine::getInstance()->getClient()->getDirectFirst()) { + CEngine::getInstance()->getClient()->microphoneOpen(true); + } + else { + CEngine::getInstance()->getClient()->setDirectFirst(false); + if ((CEngine::getInstance()->getClient()->getRadioPTTDown()) + || (CEngine::getInstance()->getClient()->getGodPTTDown()) + || (CEngine::getInstance()->getClient()->getZeusPTTDown())) { + CEngine::getInstance()->getClient()->microphoneOpen(true); + } + } + } + } + + return; + } + TRACE("enter: [%d],[%d]", userID, status); + + if ((status != MUMBLE_TS_PASSIVE) && (status != MUMBLE_TS_INVALID)) { + CEngine::getInstance()->getClient()->setDirectFirst(true); + CEngine::getInstance()->getClient()->localStartSpeaking(acre::Speaking::direct); + } + else { + CEngine::getInstance()->getClient()->setDirectFirst(false); + CEngine::getInstance()->getClient()->localStopSpeaking(acre::Speaking::direct); + CEngine::getInstance()->getClient()->setMainPTTDown(false); + CEngine::getInstance()->getClient()->setGodPTTDown(false); + CEngine::getInstance()->getClient()->setZeusPTTDown(false); + } + }); +} diff --git a/extensions/src/ACRE2Mumble/MumbleCallbacks_static.cpp b/extensions/src/ACRE2Mumble/MumbleCallbacks_static.cpp new file mode 100644 index 000000000..b6454e62d --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCallbacks_static.cpp @@ -0,0 +1,57 @@ +#include "Engine.h" +#include "Macros.h" +#include "MumbleFunctions.h" +#include "Player.h" +#include "Types.h" +#include "compat.h" + +#ifdef USE_ACRE2UI +#include "UiEngine.hpp" +#endif + +#include "Log.h" + +struct MumbleStringWrapper mumble_getName() { + static const char *name = ACRE_NAME; + + MumbleStringWrapper wrapper; + wrapper.data = name; + wrapper.size = strlen(name); + wrapper.needsReleasing = false; + + return wrapper; +} + +mumble_version_t mumble_getAPIVersion() { + return MUMBLE_PLUGIN_API_VERSION; +} + +mumble_version_t mumble_getVersion() { + return mumble_version_t{ACRE_VERSION_MAJOR, ACRE_VERSION_MINOR, ACRE_VERSION_SUBMINOR}; +} + +struct MumbleStringWrapper mumble_getAuthor() { + static const char* author = ACRE_TEAM; + + MumbleStringWrapper wrapper; + wrapper.data = author; + wrapper.size = strlen(author); + wrapper.needsReleasing = false; + + return wrapper; +} + +struct MumbleStringWrapper mumble_getDescription() { + static const char* description = ACRE_DESC; + + MumbleStringWrapper wrapper; + wrapper.data = description; + wrapper.size = strlen(description); + wrapper.needsReleasing = false; + + return wrapper; +} + +mumble_version_t mumble_getPluginFunctionsVersion() { + return MUMBLE_PLUGIN_FUNCTIONS_VERSION; +} diff --git a/extensions/src/ACRE2Mumble/MumbleClient.cpp b/extensions/src/ACRE2Mumble/MumbleClient.cpp new file mode 100644 index 000000000..d883b6076 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleClient.cpp @@ -0,0 +1,400 @@ +#include "AcreSettings.h" +#include "Engine.h" +#include "Log.h" +#include "MumbleClient.h" +#include "MumbleFunctions.h" +#include "Shlwapi.h" +#include "Types.h" +#include "compat.h" +#include "shlobj.h" + +#pragma comment(lib, "Shlwapi.lib") + +static constexpr std::int32_t invalid_mumble_channel = -1; +constexpr char default_mumble_channel[] = "ACRE"; + +extern MumbleAPI_v_1_0_x mumAPI; +extern mumble_connection_t activeConnection; +extern mumble_plugin_id_t pluginID; + +acre::Result CMumbleClient::initialize() { + setPreviousChannel(invalid_mumble_channel); + return acre::Result::ok; +} + +acre::Result CMumbleClient::setMuted(const acre::id_t id_, const bool muted_) { + (void) id_; + (void) muted_; + return acre::Result::ok; +} + +acre::Result CMumbleClient::setMuted(std::list idList_, bool muted_) { + (void) idList_; + (void) muted_; + return acre::Result::ok; +} + +acre::Result CMumbleClient::getMuted(acre::id_t id_) { + (void) id_; + return acre::Result::ok; +} + +acre::Result CMumbleClient::stop() { + if (CEngine::getInstance() != nullptr) { + CEngine::getInstance()->stop(); + this->setState(acre::State::stopping); + if (this->m_versionThreadHandle.joinable()) { + this->m_versionThreadHandle.join(); + } + this->setState(acre::State::stopped); + + mumAPI.log(pluginID, "stopped"); + } + + return acre::Result::ok; +} + +acre::Result CMumbleClient::start(const acre::id_t id_) { + CEngine::getInstance()->start(id_); + this->setInputActive(false); + this->setDirectFirst(false); + this->setMainPTTDown(false); + this->setRadioPTTDown(false); + this->setIntercomPTTDown(false); + this->setGodPTTDown(false); + this->setZeusPTTDown(false); + this->setHitTSSpeakingEvent(false); + this->setOnRadio(false); + this->setState(acre::State::running); + this->setIsX3DInitialized(false); + + mumAPI.log(pluginID, "Started"); + mumAPI.log(pluginID, ACRE_VERSION_METADATA); + + return acre::Result::ok; +} + +bool CMumbleClient::getVAD() { + mumble_transmission_mode_t transmitMode; + const mumble_error_t err = mumAPI.getLocalUserTransmissionMode(pluginID, &transmitMode); + if (err != MUMBLE_STATUS_OK) { + return false; + } + + return transmitMode == MUMBLE_TM_VOICE_ACTIVATION; +} + +acre::Result CMumbleClient::localStartSpeaking(const acre::Speaking speakingType_) { + this->localStartSpeaking(speakingType_, ""); + return acre::Result::ok; +} + +acre::Result CMumbleClient::localStartSpeaking(const acre::Speaking speakingType_, std::string radioId_) { + bool stopDirectSpeaking = false; + + const bool VADactive = this->getVAD(); + const std::int32_t speakingState = this->getSpeakingState(); + + /* Open or close the microphone. If the microphone is still active, stop direct speaking before + * starting the new PTT method: radio/god speaking. In theory this would not be needed for intercom since + * at the moment it is a direct speak with audio effect. However, it is planned to have intercoms converted + * to components and unique IDs. + */ + if ((speakingType_ == acre::Speaking::radio) || (speakingType_ == acre::Speaking::intercom) || (speakingType_ == acre::Speaking::god) || (speakingType_ == acre::Speaking::zeus)) { + if (speakingType_ == acre::Speaking::radio) { + this->setRadioPTTDown(true); + this->setOnRadio(true); + } else if (speakingType_ == acre::Speaking::intercom) { + this->setIntercomPTTDown(true); + } else if (speakingType_ == acre::Speaking::god) { + this->setGodPTTDown(true); + this->setOnRadio(true); + } else if (speakingType_ == acre::Speaking::zeus) { + this->setZeusPTTDown(true); + this->setOnRadio(true); + } + + if (!VADactive) { + if (!this->getDirectFirst()) { + this->microphoneOpen(true); + } else { + stopDirectSpeaking = true; + } + } else if (VADactive && (speakingState != MUMBLE_TS_PASSIVE) && (speakingState != MUMBLE_TS_INVALID)) { + stopDirectSpeaking = true; + } + } + + if (stopDirectSpeaking) { + CEngine::getInstance()->localStopSpeaking(); + } + CEngine::getInstance()->localStartSpeaking(speakingType_, radioId_); + return acre::Result::ok; +} + +acre::Result CMumbleClient::localStopSpeaking(const acre::Speaking speakingType_) { + bool resendDirectSpeaking = false; + + const bool VADactive = this->getVAD(); + switch (speakingType_) { + case acre::Speaking::direct: + break; + case acre::Speaking::god: + this->setGodPTTDown(false); + break; + case acre::Speaking::zeus: + this->setZeusPTTDown(false); + break; + case acre::Speaking::radio: + this->setRadioPTTDown(false); + break; + case acre::Speaking::intercom: + this->setIntercomPTTDown(false); + break; + case acre::Speaking::unknown: + this->setRadioPTTDown(false); + this->setIntercomPTTDown(false); + this->setGodPTTDown(false); + this->setZeusPTTDown(false); + break; + default: + break; + } + + if (this->getOnRadio()) { + if (!VADactive) { + if (((speakingType_ == acre::Speaking::radio) || (speakingType_ == acre::Speaking::god) || (speakingType_ == acre::Speaking::zeus)) && this->getDirectFirst()) { + this->setOnRadio(false); + resendDirectSpeaking = true; + } else { + if ((!CEngine::getInstance()->getClient()->getMainPTTDown()) + && (!CEngine::getInstance()->getClient()->getGodPTTDown()) + && (!CEngine::getInstance()->getClient()->getZeusPTTDown())) { + this->microphoneOpen(false); + } else { + resendDirectSpeaking = true; + } + } + } else { + this->setOnRadio(false); + const std::int32_t speakingState = this->getSpeakingState(); + + if ((speakingState != MUMBLE_TS_PASSIVE) && (speakingState != MUMBLE_TS_INVALID)) { + resendDirectSpeaking = true; + } + } + } else if (speakingType_ == acre::Speaking::intercom) { + const std::int32_t speakingState = this->getSpeakingState(); + if (!VADactive) { + if (!CEngine::getInstance()->getClient()->getIntercomPTTDown()) { + this->microphoneOpen(false); + } else { + resendDirectSpeaking = true; + } + } else if ((speakingState != MUMBLE_TS_PASSIVE) && (speakingState != MUMBLE_TS_INVALID)) { + resendDirectSpeaking = true; + } + } + + CEngine::getInstance()->localStopSpeaking(); + if (resendDirectSpeaking) { + CEngine::getInstance()->localStartSpeaking(acre::Speaking::direct); + } + + return acre::Result::ok; +} + +acre::Result CMumbleClient::enableMicrophone(const bool status_) { + (void) status_; + return acre::Result::ok; +} + +acre::Result CMumbleClient::playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_) { + return acre::Result::ok; +} + +std::string CMumbleClient::getUniqueId() { + return "not used"; +} + +std::string CMumbleClient::getConfigFilePath(void) { + std::string tempFolder = ".\\acre"; + if (!PathFileExistsA(tempFolder.c_str()) && !CreateDirectoryA(tempFolder.c_str(), nullptr)) { + LOG("ERROR: UNABLE TO CREATE TEMP DIR"); + } + + return tempFolder; +} + +std::string CMumbleClient::getTempFilePath(void) { + char tempPath[MAX_PATH - 14]; + GetTempPathA(sizeof(tempPath), tempPath); + std::string tempFolder = std::string(tempPath); + tempFolder.append("\\acre"); + if (!PathFileExistsA(tempFolder.c_str()) && !CreateDirectoryA(tempFolder.c_str(), nullptr)) { + LOG("ERROR: UNABLE TO CREATE TEMP DIR"); + } + + return tempFolder; +} + +acre::Result CMumbleClient::microphoneOpen(bool status_) { + const mumble_error_t res = mumAPI.requestMicrophoneActivationOvewrite(pluginID, status_); + if (res != MUMBLE_STATUS_OK) { + if (status_) { + LOG("Error toggling PTT Open: %s (%d)\n", mumble_errorMessage(res), res); + } else { + LOG("Error toggling PTT Closed: %s (%d)\n", mumble_errorMessage(res), res); + } + + return acre::Result::error; + } + + this->setInputActive(status_); + return acre::Result::ok; +} + +acre::Result CMumbleClient::unMuteAll(void) { + return acre::Result::ok; +} + +acre::Result CMumbleClient::moveToServerChannel() { + TRACE("moveToServerChannel ENTER"); + if (!CAcreSettings::getInstance()->getDisableChannelSwitch()) { + mumble_userid_t clientId; + std::vector details = getChannelDetails(); + + if (mumAPI.getLocalUserID(pluginID, activeConnection, &clientId) == MUMBLE_STATUS_OK) { + mumble_channelid_t currentChannelId = invalid_mumble_channel; + + if ((mumAPI.getChannelOfUser(pluginID, activeConnection, clientId, ¤tChannelId) == MUMBLE_STATUS_OK) && + (getPreviousChannel() == invalid_mumble_channel)) { + setPreviousChannel(currentChannelId); + } + + const mumble_channelid_t channelId = static_cast(findChannelByNames(details)); + if ((channelId != invalid_mumble_channel) && (channelId != currentChannelId)) { + std::string password; + if (!details.at(1).empty() && !details.at(0).empty()) { + password = details.at(1); + } + + mumAPI.requestUserMove(pluginID, activeConnection, clientId, channelId, password.c_str()); + } + } + } + setShouldSwitchChannel(false); + + return acre::Result::ok; +} + +acre::Result CMumbleClient::moveToPreviousChannel() { + TRACE("moveToPreviousChannel ENTER"); + if (!CAcreSettings::getInstance()->getDisableChannelSwitch()) { + mumble_userid_t clientId = -1; + + if (mumAPI.getLocalUserID(pluginID, activeConnection, &clientId) == MUMBLE_STATUS_OK) { + mumble_channelid_t currentChannelId = invalid_mumble_channel; + + if (mumAPI.getChannelOfUser(pluginID, activeConnection, clientId, ¤tChannelId) == MUMBLE_STATUS_OK) { + const mumble_channelid_t channelId = static_cast(getPreviousChannel()); + if (channelId != invalid_mumble_channel && channelId != currentChannelId) { + mumAPI.requestUserMove(pluginID, activeConnection, clientId, channelId, ""); + } + } + } + setPreviousChannel(invalid_mumble_channel); + } + + return acre::Result::ok; +} + +uint64_t CMumbleClient::findChannelByNames(std::vector details_) { + TRACE("findChannelByNames ENTER"); + mumble_channelid_t *channelList = nullptr; + std::size_t channelCount = 0U; + + if (mumAPI.getAllChannels(pluginID, activeConnection, &channelList, &channelCount) == MUMBLE_STATUS_OK) { + mumble_channelid_t channelId = invalid_mumble_channel; + mumble_channelid_t defaultChannelId = invalid_mumble_channel; + std::map channelMap; + std::string name = details_.at(2); + if (!details_.at(0).empty()) { + name = details_.at(0); + } + + for (std::int32_t idx = 0U; idx < channelCount; idx++) { + channelId = *channelList + idx; + const char *channelName = nullptr; + + if (mumAPI.getChannelName(pluginID, activeConnection, channelId, &channelName) == MUMBLE_STATUS_OK) { + // Copy the channel name into a std::string and then get rid of the Mumble resource again + std::string channelNameString(channelName); + mumAPI.freeMemory(pluginID, (void *) channelName); + + if (channelNameString.find(default_mumble_channel) != -1 || (!details_.at(0).empty() && channelNameString == name)) { + if (channelNameString == default_mumble_channel) { + defaultChannelId = channelId; + } + channelMap.emplace(channelId, channelNameString); + } + } + } + + mumAPI.freeMemory(pluginID, channelList); + + mumble_channelid_t bestChannelId = invalid_mumble_channel; + int32_t bestMatches = 0; + int32_t bestDistance = 10; + for (auto &element : channelMap) { + std::string fullChannelName = element.second; + // Full comparison + if (fullChannelName.compare(name) == 0) { + bestChannelId = element.first; + break; + } + const std::string cleanChannelName = removeSubstrings(fullChannelName, default_mumble_channel); + // Word comparison + const int32_t matches = getWordMatches(cleanChannelName, name); + if (matches > bestMatches) { + bestMatches = matches; + bestChannelId = element.first; + continue; + } + // Char comparison + const int32_t distance = levenshteinDistance(cleanChannelName, name); + if (distance <= bestDistance) { + bestDistance = distance; + bestChannelId = element.first; + } + } + if (bestChannelId == invalid_mumble_channel) { + if (!details_.at(0).empty()) { + details_.at(0).clear(); + bestChannelId = static_cast(findChannelByNames(details_)); + } else if (defaultChannelId != invalid_mumble_channel) { + bestChannelId = defaultChannelId; + } + } + return bestChannelId; + } + + return 0; +} + +acre::Result CMumbleClient::updateChannelDetails(std::vector details_) { + setChannelDetails(details_); + if (!details_.empty()) { + updateShouldSwitchChannel(true); + } + return acre::Result::ok; +} + +acre::Result CMumbleClient::updateShouldSwitchChannel(const bool state_) { + setShouldSwitchChannel(state_); + return acre::Result::ok; +} + +bool CMumbleClient::shouldSwitchChannel() { + return getShouldSwitchChannel(); +} diff --git a/extensions/src/ACRE2Mumble/MumbleClient.h b/extensions/src/ACRE2Mumble/MumbleClient.h new file mode 100644 index 000000000..74c4137c7 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleClient.h @@ -0,0 +1,80 @@ +#pragma once + +#include "IClient.h" +#include "MumbleFunctions.h" + +#include +#include +#include + +class CMumbleClient : public IClient { +public: + CMumbleClient() = default; + ~CMumbleClient() final = default; + + acre::Result initialize(void) final; + + acre::Result setMuted(const acre::id_t id_, const bool muted_) final; + acre::Result setMuted(std::list idList_, const bool muted_) final; + + acre::Result getMuted(const acre::id_t id_) final; + + acre::Result stop() final; + acre::Result start(const acre::id_t id_) final; + + acre::Result enableMicrophone(const bool status_) final; + + bool getVAD(); + + /*! + * \brief Handles local player starting speaking. + * + * \param[in] speakingType_ ACRE speaking type + * + * \return acre::Result::ok if operation successful + */ + acre::Result localStartSpeaking(const acre::Speaking speakingType_) final; + + /*! + * \brief Handles local player starting speaking. + * + * \param[in] speakingType_ ACRE speaking type + * \param[in] radioId_ Unique radio ideintifier + * + * \return acre::Result::ok if operation successful + */ + acre::Result localStartSpeaking(const acre::Speaking speakingType_, std::string radioId_) final; + + /*! + * \brief Handles local player stopping speaking. + * + * \param[in] speakingType_ ACRE speaking type + * + * \return acre::Result::ok if operation successful + */ + acre::Result localStopSpeaking(const acre::Speaking speakingType_) final; + + std::string getTempFilePath(void) final; + std::string getConfigFilePath(void) final; + + acre::Result playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_) final; + + std::string getUniqueId() final; + + acre::Result microphoneOpen(const bool status_) final; + + acre::Result unMuteAll(void) final; + + acre::Result moveToServerChannel() final; + acre::Result moveToPreviousChannel() final; + uint64_t findChannelByNames(std::vector details_) final; + + acre::Result updateChannelDetails(std::vector details_) final; + acre::Result updateShouldSwitchChannel(const bool state_) final; + bool shouldSwitchChannel() final; + +private: + bool currentStatus; + std::thread m_versionThreadHandle; + char *m_vadLevel = nullptr; +}; diff --git a/extensions/src/ACRE2Mumble/MumbleCommandServer.cpp b/extensions/src/ACRE2Mumble/MumbleCommandServer.cpp new file mode 100644 index 000000000..2729f6d16 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCommandServer.cpp @@ -0,0 +1,88 @@ +#include "Log.h" +#include "MumbleClient.h" +#include "MumbleCommandServer.h" +#include "MumbleFunctions.h" +#include "TextMessage.h" + +#include + +extern MumbleAPI_v_1_0_x mumAPI; +extern mumble_connection_t activeConnection; +extern mumble_plugin_id_t pluginID; + +acre::Result CMumbleCommandServer::initialize() { + TRACE("enter"); + + return acre::Result::ok; +} + +acre::Result CMumbleCommandServer::shutdown() { + TRACE("enter"); + + return acre::Result::ok; +} + +acre::Result CMumbleCommandServer::sendMessage(IMessage *msg) { + std::lock_guard guard(*this); + + mumble_userid_t *channelUsers = nullptr; + size_t userCount = 0U; + mumble_channelid_t currentChannel = 0; + + mumble_error_t err = mumAPI.getChannelOfUser(pluginID, activeConnection, this->getId(), ¤tChannel); + if (err != MUMBLE_STATUS_OK) { + LOG("ERROR, UNABLE TO GET CHANNEL OF USER: %s (%d)", mumble_errorMessage(err), err); + return acre::Result::error; + } + + err = mumAPI.getUsersInChannel(pluginID, activeConnection, currentChannel, &channelUsers, &userCount); + if (err != MUMBLE_STATUS_OK) { + LOG("ERROR, UNABLE TO GET USERS IN CHANNEL: %s (%d)", mumble_errorMessage(err), err); + return acre::Result::error; + } + + err = mumAPI.sendData(pluginID, activeConnection, channelUsers, userCount, (const uint8_t *) msg->getData(), msg->getLength(), "ACRE2"); + if (err != MUMBLE_STATUS_OK) { + LOG("ERROR, UNABLE TO SEND MESSAGE DATA: %s (%d)", mumble_errorMessage(err), err); + return acre::Result::error; + } + err = mumAPI.freeMemory(pluginID, (void *) channelUsers); + if (err != MUMBLE_STATUS_OK) { + LOG("ERROR, UNABLE TO FREE CHANNEL USER LIST: %s (%d)", mumble_errorMessage(err), err); + return acre::Result::error; + } + + delete msg; + + return acre::Result::ok; +} + +acre::Result CMumbleCommandServer::handleMessage(unsigned char *data) { + return this->handleMessage(data, strlen((char *) data)); +} + +acre::Result CMumbleCommandServer::handleMessage(unsigned char *data, size_t length) { + CTextMessage *msg = nullptr; + // TRACE("recv: [%s]", data); + msg = new (std::nothrow) CTextMessage((char *) data, length); + if (CEngine::getInstance()->getRpcEngine() && (msg != nullptr)) { + CEngine::getInstance()->getRpcEngine()->runProcedure((IServer *) this, (IMessage *) msg); + } + return acre::Result::ok; +} + +acre::Result CMumbleCommandServer::release() { + return acre::Result::ok; +} + +// +// constructor/destructor foo +// +CMumbleCommandServer::CMumbleCommandServer(const acre::id_t id) { + this->setId(id); +} + +CMumbleCommandServer::CMumbleCommandServer() { + this->setCommandId(0); + this->setConnected(true); +} diff --git a/extensions/src/ACRE2Mumble/MumbleCommandServer.h b/extensions/src/ACRE2Mumble/MumbleCommandServer.h new file mode 100644 index 000000000..f34a4a110 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleCommandServer.h @@ -0,0 +1,40 @@ +#pragma once + +#include "Engine.h" +#include "IMessage.h" +#include "IServer.h" +#include "Lockable.h" +#include "Macros.h" +#include "MumbleFunctions.h" +#include "Types.h" +#include "compat.h" + +class CMumbleCommandServer : public IServer, public CLockable { +public: + CMumbleCommandServer(void); + CMumbleCommandServer(const acre::id_t id); + ~CMumbleCommandServer(void) final = default; + + acre::Result initialize(void) final; + acre::Result shutdown(void) final; + + acre::Result sendMessage(IMessage *msg) final; + acre::Result handleMessage(unsigned char *msg) final; + acre::Result handleMessage(unsigned char *msg, size_t length) final; + + acre::Result release(void) final; + + inline void setCommandId(mumble_plugin_id_t value) noexcept { m_commandId = value; } + inline mumble_plugin_id_t getCommandId() const noexcept { return m_commandId; } + + inline void setConnected(const bool value) final { m_connected = value; } + inline bool getConnected() const final { return m_connected; } + + inline void setId(const acre::id_t value) final { m_id = value; } + inline acre::id_t getId() const final { return m_id; } + +private: + acre::id_t m_id; + bool m_connected; + mumble_plugin_id_t m_commandId; +}; diff --git a/extensions/src/ACRE2Mumble/MumbleEventLoop.cpp b/extensions/src/ACRE2Mumble/MumbleEventLoop.cpp new file mode 100644 index 000000000..ae49effba --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleEventLoop.cpp @@ -0,0 +1,85 @@ +#include "MumbleEventLoop.h" + +namespace acre { + + MumbleEventLoop& MumbleEventLoop::getInstance() { + static MumbleEventLoop loop; + + return loop; + } + + MumbleEventLoop::MumbleEventLoop() { + } + + void MumbleEventLoop::start() { + if (m_thread.joinable()) { + throw std::runtime_error("ACRE2 - MumbleEventLoop: Attempted to start already-running event loop. Must call stop first."); + } + + { + std::lock_guard guard(m_lock); + m_keepRunning = true; + } + + m_thread = std::thread(&MumbleEventLoop::run, this); + } + + void MumbleEventLoop::stop() { + { + std::lock_guard guard(m_lock); + m_keepRunning = false; + + m_waiter.notify_all(); + } + + if (m_thread.joinable()) { + // Wait until the worker thread has finished + m_thread.join(); + } + } + + void MumbleEventLoop::queue(const std::function& callable) { + std::lock_guard guard(m_lock); + + m_queuedFunctions.push_back(callable); + + m_waiter.notify_all(); + } + + void MumbleEventLoop::queue(std::function&& callable) { + std::unique_lock guard(m_lock); + + m_queuedFunctions.push_back(std::move(callable)); + + m_waiter.notify_all(); + } + + void MumbleEventLoop::run() { + std::unique_lock guard(m_lock); + + while (m_keepRunning) { + // Process all pending events + while (!m_queuedFunctions.empty()) { + std::function currentFunc = std::move(m_queuedFunctions.front()); + m_queuedFunctions.pop_front(); + + // Execute the current funtion. However, during the time this function is executing, there is no need to + // keep holding m_lock. By releasing it for that time, we allow further functions to be queued by other threads. + // This gets especially important if the called function will call a Mumble API function. This function will require + // to run in Mumble's main thread but if that thread is in turn waiting until a new event is queued (in one of the + // plugin callbacks in this plugin), we end up with a deadlock. + guard.unlock(); + currentFunc(); + guard.lock(); + } + + if (!m_keepRunning) { + break; + } + + // Wait for something to happen. Note that during the waiting the lock is released and + // re-aquired once the waiting ends + m_waiter.wait(guard); + } + } +} diff --git a/extensions/src/ACRE2Mumble/MumbleEventLoop.h b/extensions/src/ACRE2Mumble/MumbleEventLoop.h new file mode 100644 index 000000000..f46656bf0 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleEventLoop.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace acre { + + /** + * This class represents a separate event loop that allows to process events in their own thread. This is needed, because + * the synchronization of the Mumble API works by executing all API functions within Mumble's main thread, which also + * happens to be the thread almost all plugin callbacks are called from. Thus, blocking these callbacks (e.g. due to waiting + * on obtaining a lock) and simultaneously executing an API function from the thread that is the cause of the blocking (e.g. + * because it is holding the mentioned lock) will yield a deadlock, since the API call will block until it can be executed + * in Mumble's main thread, but the main thread is currently stuck in the plugin callback. + * Mumble does have a mechanism built-in that prevents an actual deadlock from happening (using a timed-wait) but such calls + * will still cause a lag in the program and will also cause the API call to fail (with a timeout error). Thus, these + * situations should be avoided. + * This is achieved by not executing the plugin callbacks synchronously but instead queuing them into this event loop for + * async execution. Therefore Mumble's main thread will not be blocked and we avoid this entire situation. + * + * Note that when shutting the event loop down from within a plugin callback (typically the shutdown callback), the + * abovementioned situation can still easily occur (since we have to wait for the event loop to end before returning + * from that callback). However, due to Mumble's deadlock prevention, this shouldn't cause any major issues. It could lead to + * small delays during shutdown, though (and some Mumble API calls failing under that circumstances). + */ + class MumbleEventLoop { + public: + MumbleEventLoop(); + + static MumbleEventLoop& getInstance(); + + void start(); + void stop(); + + void queue(const std::function& callable); + void queue(std::function&& callable); + + protected: + std::mutex m_lock; + std::condition_variable m_waiter; + bool m_keepRunning = true; + std::deque> m_queuedFunctions; + std::thread m_thread; + + void run(); + }; + +} diff --git a/extensions/src/ACRE2Mumble/MumbleFunctions.h b/extensions/src/ACRE2Mumble/MumbleFunctions.h new file mode 100644 index 000000000..c5860ab87 --- /dev/null +++ b/extensions/src/ACRE2Mumble/MumbleFunctions.h @@ -0,0 +1,5 @@ +#pragma once + +#include "MumbleAPI_v_1_0_x.h" +#define MUMBLE_PLUGIN_NO_DEFAULT_FUNCTION_DEFINITIONS +#include "MumblePlugin_v_1_1_x.h" diff --git a/extensions/src/ACRE2Mumble/acre2_mumble_dllmain.cpp b/extensions/src/ACRE2Mumble/acre2_mumble_dllmain.cpp new file mode 100644 index 000000000..bcc4b003c --- /dev/null +++ b/extensions/src/ACRE2Mumble/acre2_mumble_dllmain.cpp @@ -0,0 +1,6 @@ +#include "MumbleFunctions.h" + +MumbleAPI_v_1_0_x mumAPI; + +#pragma comment(lib, "x3daudio.lib") +#pragma comment(lib, "shlwapi.lib") diff --git a/extensions/src/ACRE2Mumble/mumble_includes/MumbleAPI_v_1_0_x.h b/extensions/src/ACRE2Mumble/mumble_includes/MumbleAPI_v_1_0_x.h new file mode 100644 index 000000000..7e97f6643 --- /dev/null +++ b/extensions/src/ACRE2Mumble/mumble_includes/MumbleAPI_v_1_0_x.h @@ -0,0 +1,532 @@ +// Copyright 2021 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +/// This header file contains the definition of Mumble's API + +#ifndef EXTERNAL_MUMBLE_PLUGIN_API_H_ +#define EXTERNAL_MUMBLE_PLUGIN_API_H_ + +#include "PluginComponents_v_1_0_x.h" +#include + + +// API version +#define MUMBLE_PLUGIN_API_MAJOR_MACRO 1 +#define MUMBLE_PLUGIN_API_MINOR_MACRO 0 +#define MUMBLE_PLUGIN_API_PATCH_MACRO 3 + +const int32_t MUMBLE_PLUGIN_API_MAJOR = MUMBLE_PLUGIN_API_MAJOR_MACRO; +const int32_t MUMBLE_PLUGIN_API_MINOR = MUMBLE_PLUGIN_API_MINOR_MACRO; +const int32_t MUMBLE_PLUGIN_API_PATCH = MUMBLE_PLUGIN_API_PATCH_MACRO; +const mumble_version_t MUMBLE_PLUGIN_API_VERSION = { MUMBLE_PLUGIN_API_MAJOR, MUMBLE_PLUGIN_API_MINOR, + MUMBLE_PLUGIN_API_PATCH }; + +// Create macro for casting the pointer to the API object to the proper struct. +// Note that this must only be used if the API uses MUMBLE_PLUGIN_API_VERSION of the API. +#define MUMBLE_CONCAT_HELPER(a, b) a##_##b +#define MUMBLE_CONCAT(a, b) MUMBLE_CONCAT_HELPER(a, b) +#define MUMBLE_API_STRUCT \ + MUMBLE_CONCAT(MumbleAPI_v, \ + MUMBLE_CONCAT(MUMBLE_PLUGIN_API_MAJOR_MACRO, MUMBLE_CONCAT(MUMBLE_PLUGIN_API_MINOR_MACRO, x))) +#define MUMBLE_API_CAST(ptrName) (*((struct MUMBLE_API_STRUCT *) ptrName)) + + +struct MumbleAPI_v_1_0_x { + ///////////////////////////////////////////////////////// + ////////////////////// GENERAL NOTES //////////////////// + ///////////////////////////////////////////////////////// + // + // All functions that take in a connection as a paremeter may only be called **after** the connection + // has finished synchronizing. The only exception from this is isConnectionSynchronized. + // + // Strings returned by the API are UTF-8 encoded + // Strings passed to the API are expected to be UTF-8 encoded + // + // All API functions are synchronized and will be executed in Mumble's "main thread" from which most plugin + // callbacks are called as well. Note however that an API call is BLOCKING if invoked from a different + // thread. This means that they can cause deadlocks if used without caution. An example that will lead + // to a deadlock is: + // - plugin callback gets called from the main thread + // - callback messages a separate thread to do something and waits for the action to have completed + // - Separate thread calls an API function + // - The function blocks and waits to be executed in the main thread which is currently blocked waiting + // - deadlock + + + // -------- Memory management -------- + + /// Frees the given pointer. + /// + /// @param callerID The ID of the plugin calling this function + /// @param pointer The pointer to free + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* freeMemory)(mumble_plugin_id_t callerID, const void* pointer); + + + + // -------- Getter functions -------- + + /// Gets the connection ID of the server the user is currently active on (the user's audio output is directed at). + /// + /// @param callerID The ID of the plugin calling this function + /// @param[out] connection A pointer to the memory location the ID should be written to + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then it is valid to access + /// the value of the provided pointer + mumble_error_t(PLUGIN_CALLING_CONVENTION* getActiveServerConnection)(mumble_plugin_id_t callerID, + mumble_connection_t* connection); + + /// Checks whether the given connection has finished initializing yet. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param[out] A pointer to the boolean variable that'll hold the info whether the server has finished + /// synchronization yet after this function has executed successfully. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* isConnectionSynchronized)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + bool* synchronized); + + /// Fills in the information about the local user. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param[out] userID A pointer to the memory the user's ID shall be written to + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getLocalUserID)(mumble_plugin_id_t callerID, + mumble_connection_t connection, mumble_userid_t* userID); + + /// Fills in the information about the given user's name. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param userID The user's ID whose name should be obtained + /// @param[out] userName A pointer to where the pointer to the allocated string (C-encoded) should be written to. + /// The allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getUserName)(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_userid_t userID, const char** userName); + + /// Fills in the information about the given channel's name. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param channelID The channel's ID whose name should be obtained + /// @param[out] channelName A pointer to where the pointer to the allocated string (C-ecoded) should be written to. + /// The allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getChannelName)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t channelID, const char** channelName); + + /// Gets an array of all users that are currently connected to the provided server. Passing a nullptr as any of the + /// out-parameter will prevent that property to be set/allocated. If you are only interested in the user count you + /// can thus pass nullptr as the users parameter and save time on allocating + freeing the channels-array while + /// still getting the size out. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param[out] users A pointer to where the pointer of the allocated array shall be written. The + /// allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @param[out] userCount A pointer to where the size of the allocated user-array shall be written to + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getAllUsers)(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_userid_t** users, size_t* userCount); + + /// Gets an array of all channels on the provided server. Passing a nullptr as any of the out-parameter will prevent + /// that property to be set/allocated. If you are only interested in the channel count you can thus pass nullptr as + /// the channels parameter and save time on allocating + freeing the channels-array while still getting the size + /// out. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param[out] channels A pointer to where the pointer of the allocated array shall be written. The + /// allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @param[out] channelCount A pointer to where the size of the allocated channel-array shall be written to + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getAllChannels)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t** channels, size_t* channelCount); + + /// Gets the ID of the channel the given user is currently connected to. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param userID The ID of the user to search for + /// @param[out] A pointer to where the ID of the channel shall be written + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getChannelOfUser)(mumble_plugin_id_t callerID, + mumble_connection_t connection, mumble_userid_t userID, + mumble_channelid_t* channel); + + /// Gets an array of all users in the specified channel. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param channelID The ID of the channel whose users shall be retrieved + /// @param[out] userList A pointer to where the pointer of the allocated array shall be written. The allocated + /// memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be allocated if + /// this function returns STATUS_OK. + /// @param[out] userCount A pointer to where the size of the allocated user-array shall be written to + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getUsersInChannel)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t channelID, + mumble_userid_t** userList, size_t* userCount); + + /// Gets the current transmission mode of the local user. + /// + /// @param callerID The ID of the plugin calling this function + /// @param[out] transmissionMode A pointer to where the transmission mode shall be written. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getLocalUserTransmissionMode)( + mumble_plugin_id_t callerID, mumble_transmission_mode_t* transmissionMode); + + /// Checks whether the given user is currently locally muted. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param userID The ID of the user to check for + /// @param[out] muted A pointer to where the local mute state of that user shall be written + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* isUserLocallyMuted)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_userid_t userID, bool* muted); + + /// Checks whether the local user is currently muted. + /// + /// @param callerID The ID of the plugin calling this function + /// @param[out] muted A pointer to where the mute state of the local user shall be written + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* isLocalUserMuted)(mumble_plugin_id_t callerID, bool* muted); + + /// Checks whether the local user is currently deafened. + /// + /// @param callerID The ID of the plugin calling this function + /// @param[out] deafened A pointer to where the deaf state of the local user shall be written + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* isLocalUserDeafened)(mumble_plugin_id_t callerID, bool* deafened); + + /// Gets the hash of the given user (can be used to recognize users between restarts) + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param userID The ID of the user to search for + /// @param[out] hash A pointer to where the pointer to the allocated string (C-encoded) should be written to. The + /// allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getUserHash)(mumble_plugin_id_t callerID, mumble_connection_t connection, + mumble_userid_t userID, const char** hash); + + /// Gets the hash of the server for the given connection (can be used to recognize servers between restarts) + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection + /// @param[out] hash A pointer to where the pointer to the allocated string (C-encoded) should be written to. The + /// allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getServerHash)(mumble_plugin_id_t callerID, + mumble_connection_t connection, const char** hash); + + /// Gets the comment of the given user. Note that a user might have a comment configured that hasn't been + /// synchronized to this client yet. In this case this function will return EC_UNSYNCHRONIZED_BLOB. As of now there + /// is now way to request the synchronization to happen via the Plugin-API. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection + /// @param userID the ID of the user whose comment should be obtained + /// @param[out] comment A pointer to where the pointer to the allocated string (C-encoded) should be written to. The + /// allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getUserComment)(mumble_plugin_id_t callerID, + mumble_connection_t connection, mumble_userid_t userID, + const char** comment); + + /// Gets the description of the given channel. Note that a channel might have a description configured that hasn't + /// been synchronized to this client yet. In this case this function will return EC_UNSYNCHRONIZED_BLOB. As of now + /// there is now way to request the synchronization to happen via the Plugin-API. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection + /// @param channelID the ID of the channel whose comment should be obtained + /// @param[out] description A pointer to where the pointer to the allocated string (C-encoded) should be written to. + /// The allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will only be + /// allocated if this function returns STATUS_OK. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* getChannelDescription)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + mumble_channelid_t channelID, + const char** description); + + + // -------- Request functions -------- + + /// Requests Mumble to set the local user's transmission mode to the specified one. If you only need to temporarily + /// set the transmission mode to continous, use requestMicrophoneActivationOverwrite instead as this saves you the + /// work of restoring the previous state afterwards. + /// + /// @param callerID The ID of the plugin calling this function + /// @param transmissionMode The requested transmission mode + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* requestLocalUserTransmissionMode)( + mumble_plugin_id_t callerID, mumble_transmission_mode_t transmissionMode); + + /// Requests Mumble to move the given user into the given channel + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param userID The ID of the user that shall be moved + /// @param channelID The ID of the channel to move the user to + /// @param password The password of the target channel (UTF-8 encoded as a C-string). Pass NULL if the target + /// channel does not require a password for entering + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* requestUserMove)(mumble_plugin_id_t callerID, + mumble_connection_t connection, mumble_userid_t userID, + mumble_channelid_t channelID, const char* password); + + /// Requests Mumble to overwrite the microphone activation so that the microphone is always on (same as if the user + /// had chosen the continous transmission mode). If a plugin requests this overwrite, it is responsible for + /// deactivating the overwrite again once it is no longer required + /// + /// @param callerID The ID of the plugin calling this function + /// @param activate Whether to activate the overwrite (false deactivates an existing overwrite) + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* requestMicrophoneActivationOvewrite)(mumble_plugin_id_t callerID, + bool activate); + + /// Requests Mumble to set the local mute state of the given client. Note that this only affects the **local** mute + /// state opposed to a server-mute (client is globally muted by the server) or the client's own mute-state (client + /// has muted its microphone and thus isn't transmitting any audio). Furthermore it must be noted that muting the + /// local user with this function does not work (it doesn't make sense). If you try to do so, this function will + /// fail. In order to make this work, this function will also fail if the server has not finished synchronizing with + /// the client yet. For muting the local user, use requestLocalUserMute instead. + /// + /// @param callerID The ID of the plugin calling this function. + /// @param connection The ID of the server-connection to use as a context + /// @param userID The ID of the user that shall be muted + /// @param muted Whether to locally mute the given client (opposed to unmuting it) + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* requestLocalMute)(mumble_plugin_id_t callerID, + mumble_connection_t connection, mumble_userid_t userID, + bool muted); + + /// Requests Mumble to set the mute state of the local user. In the UI this is referred to as "self-mute". + /// + /// @param callerID The ID of the plugin calling this function. + /// @param muted Whether to locally mute the local user (opposed to unmuting it) + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* requestLocalUserMute)(mumble_plugin_id_t callerID, bool muted); + + /// Requests Mumble to set the deaf state of the local user. In the UI this is referred to as "self-deaf". + /// + /// @param callerID The ID of the plugin calling this function. + /// @param deafened Whether to locally deafen the local user (opposed to undeafening it) + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* requestLocalUserDeaf)(mumble_plugin_id_t callerID, bool deafened); + + /// Sets the comment of the local user + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection + /// @param comment The new comment to use (C-encoded). A subset of HTML formatting is supported. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer + /// may be accessed + mumble_error_t(PLUGIN_CALLING_CONVENTION* requestSetLocalUserComment)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + const char* comment); + + + + // -------- Find functions -------- + + /// Fills in the information about a user with the specified name, if such a user exists. The search is + /// case-sensitive. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param userName The respective user's name + /// @param[out] userID A pointer to the memory the user's ID shall be written to + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer may + /// be accessed. + mumble_error_t(PLUGIN_CALLING_CONVENTION* findUserByName)(mumble_plugin_id_t callerID, + mumble_connection_t connection, const char* userName, + mumble_userid_t* userID); + + /// Fills in the information about a channel with the specified name, if such a channel exists. The search is + /// case-sensitive. + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to use as a context + /// @param channelName The respective channel's name + /// @param[out] channelID A pointer to the memory the channel's ID shall be written to + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer may + /// be accessed. + mumble_error_t(PLUGIN_CALLING_CONVENTION* findChannelByName)(mumble_plugin_id_t callerID, + mumble_connection_t connection, + const char* channelName, + mumble_channelid_t* channelID); + + + + // -------- Settings -------- + + /// Fills in the current value of the setting with the given key. Note that this function can only be used for + /// settings whose value is a bool! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param[out] outValue A pointer to the memory the setting's value shall be written to. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer may + /// be accessed. + mumble_error_t(PLUGIN_CALLING_CONVENTION* getMumbleSetting_bool)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, bool* outValue); + + /// Fills in the current value of the setting with the given key. Note that this function can only be used for + /// settings whose value is an int! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param[out] outValue A pointer to the memory the setting's value shall be written to. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer may + /// be accessed. + mumble_error_t(PLUGIN_CALLING_CONVENTION* getMumbleSetting_int)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, int64_t* outValue); + + /// Fills in the current value of the setting with the given key. Note that this function can only be used for + /// settings whose value is a double! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param[out] outValue A pointer to the memory the setting's value shall be written to. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer may + /// be accessed. + mumble_error_t(PLUGIN_CALLING_CONVENTION* getMumbleSetting_double)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, double* outValue); + + /// Fills in the current value of the setting with the given key. Note that this function can only be used for + /// settings whose value is a String! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param[out] outValue The memory address to which the pointer to the setting's value (the String) will be + /// written. The allocated memory has to be freed by a call to freeMemory by the plugin eventually. The memory will + /// only be allocated if this function returns STATUS_OK. + /// @returns The error code. If everything went well, STATUS_OK will be returned. Only then the passed pointer may + /// be accessed. + mumble_error_t(PLUGIN_CALLING_CONVENTION* getMumbleSetting_string)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, + const char** outValue); + + + /// Sets the value of the setting with the given key. Note that this function can only be used for settings whose + /// value is a bool! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param value The value that should be set for the given setting + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* setMumbleSetting_bool)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, bool value); + + /// Sets the value of the setting with the given key. Note that this function can only be used for settings whose + /// value is an int! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param value The value that should be set for the given setting + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* setMumbleSetting_int)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, int64_t value); + + /// Sets the value of the setting with the given key. Note that this function can only be used for settings whose + /// value is a double! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param value The value that should be set for the given setting + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* setMumbleSetting_double)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, double value); + + /// Sets the value of the setting with the given key. Note that this function can only be used for settings whose + /// value is a string! + /// + /// @param callerID The ID of the plugin calling this function + /// @param key The key to the desired setting + /// @param value The value that should be set for the given setting + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* setMumbleSetting_string)(mumble_plugin_id_t callerID, + mumble_settings_key_t key, const char* value); + + + + // -------- Miscellaneous -------- + + /// Sends the provided data to the provided client(s). This kind of data can only be received by another plugin + /// active on that client. The sent data can be seen by any active plugin on the receiving client. Therefore the + /// sent data must not contain sensitive information or anything else that shouldn't be known by others. + /// + /// NOTE: Messages sent via this API function are rate-limited by the server. If the rate-limit is hit, the message + /// will be dropped without an error message. The rate-limiting is global (e.g. it doesn't matter which plugin sent + /// the respective messages - they all count to the same limit). + /// Therefore if you have multiple messages to send, you should consider sending them asynchronously one at a time + /// with a little delay in between (~1 second). + /// + /// @param callerID The ID of the plugin calling this function + /// @param connection The ID of the server-connection to send the data through (the server the given users are on) + /// @param users An array of user IDs to send the data to + /// @param userCount The size of the provided user-array + /// @param data The data array that shall be sent. This can be an arbitrary sequence of bytes. Note that the size of + /// is restricted to <= 1KB. + /// @param dataLength The length of the data array + /// @param dataID The ID of the sent data. This has to be used by the receiving plugin(s) to figure out what to do + /// with the data. This has to be a C-encoded String. It is recommended that the ID starts with a plugin-specific + /// prefix in order to avoid name clashes. Note that the size of this string is restricted to <= 100 bytes. + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* sendData)(mumble_plugin_id_t callerID, mumble_connection_t connection, + const mumble_userid_t* users, size_t userCount, + const uint8_t* data, size_t dataLength, const char* dataID); + + /// Logs the given message (typically to Mumble's console). All passed strings have to be UTF-8 encoded. + /// + /// @param callerID The ID of the plugin calling this function + /// @param message The message to log + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* log)(mumble_plugin_id_t callerID, const char* message); + + /// Plays the provided sample. It uses libsndfile as a backend so the respective file format needs to be supported + /// by it in order for this to work out (see http://www.mega-nerd.com/libsndfile/). + /// + /// @param callerID The ID of the plugin calling this function + /// @param samplePath The path to the sample that shall be played (UTF-8 encoded) + /// @returns The error code. If everything went well, STATUS_OK will be returned. + mumble_error_t(PLUGIN_CALLING_CONVENTION* playSample)(mumble_plugin_id_t callerID, const char* samplePath); +}; + +typedef struct MumbleAPI_v_1_0_x mumble_api_t; + +#endif // EXTERNAL_MUMBLE_PLUGIN_API_H_ diff --git a/extensions/src/ACRE2Mumble/mumble_includes/MumblePlugin_v_1_1_x.h b/extensions/src/ACRE2Mumble/mumble_includes/MumblePlugin_v_1_1_x.h new file mode 100644 index 000000000..c2f78580e --- /dev/null +++ b/extensions/src/ACRE2Mumble/mumble_includes/MumblePlugin_v_1_1_x.h @@ -0,0 +1,477 @@ +// Copyright 2021 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +/// This header file specifies the Mumble plugin interface + +#ifndef EXTERNAL_MUMBLE_PLUGIN_H_ +#define EXTERNAL_MUMBLE_PLUGIN_H_ + +#include "PluginComponents_v_1_0_x.h" +#include +#include +#include + +#if defined(__GNUC__) && !defined(__MINGW32__) // GCC on Unix-like systems +# define PLUGIN_EXPORT __attribute__((visibility("default"))) +#elif defined(_MSC_VER) +# define PLUGIN_EXPORT __declspec(dllexport) +#elif defined(__MINGW32__) +# define PLUGIN_EXPORT __attribute__((dllexport)) +#else +# error No PLUGIN_EXPORT definition available +#endif + + +// Plugin functions version +#define MUMBLE_PLUGIN_FUNCTIONS_MAJOR_MACRO 1 +#define MUMBLE_PLUGIN_FUNCTIONS_MINOR_MACRO 1 +#define MUMBLE_PLUGIN_FUNCTIONS_PATCH_MACRO 0 + +const int32_t MUMBLE_PLUGIN_FUNCTIONS_MAJOR = MUMBLE_PLUGIN_FUNCTIONS_MAJOR_MACRO; +const int32_t MUMBLE_PLUGIN_FUNCTIONS_MINOR = MUMBLE_PLUGIN_FUNCTIONS_MINOR_MACRO; +const int32_t MUMBLE_PLUGIN_FUNCTIONS_PATCH = MUMBLE_PLUGIN_FUNCTIONS_PATCH_MACRO; +const mumble_version_t MUMBLE_PLUGIN_FUNCTIONS_VERSION = { MUMBLE_PLUGIN_FUNCTIONS_MAJOR, MUMBLE_PLUGIN_FUNCTIONS_MINOR, + MUMBLE_PLUGIN_FUNCTIONS_PATCH }; + +#ifdef __cplusplus +extern "C" { +#endif + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////// MANDATORY FUNCTIONS /////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + /// Gets called right after loading the plugin in order to let the plugin initialize. + /// + /// Registers the ID of this plugin. + /// @param id The ID for this plugin. This is the ID Mumble will reference this plugin with + /// and by which this plugin can identify itself when communicating with Mumble. + /// @returns The status of the initialization. If everything went fine, return STATUS_OK + PLUGIN_EXPORT mumble_error_t PLUGIN_CALLING_CONVENTION mumble_init(mumble_plugin_id_t id); + + /// Gets called when unloading the plugin in order to allow it to clean up after itself. + /// Note that it is still safe to call API functions from within this callback. + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_shutdown(); + + /// Gets the name of the plugin. + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @returns A String-wrapper containing the requested name + PLUGIN_EXPORT struct MumbleStringWrapper PLUGIN_CALLING_CONVENTION mumble_getName(); + + /// Gets the Version of the plugin-API this plugin intends to use. + /// Mumble will decide whether this plugin is loadable or not based on the return value of this function. + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @returns The respective API Version + PLUGIN_EXPORT mumble_version_t PLUGIN_CALLING_CONVENTION mumble_getAPIVersion(); + + /// Provides the MumbleAPI struct to the plugin. This struct contains function pointers that can be used + /// to interact with the Mumble client. It is up to the plugin to store this struct somewhere if it wants to make use + /// of it at some point. + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @param api A pointer to the MumbleAPI struct. The API struct must be cast to the version corresponding to the + /// user API version. If your plugin is e.g. using the 1.0.x API, then you have to cast this pointer to + /// MumbleAPI_v_1_0_x. Note also that you **must not store this pointer**. It will become invalid. Therefore + /// you have to copy the struct in order to use it later on. + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_registerAPIFunctions(void* apiStruct); + + /// Releases the resource pointed to by the given pointer. If the respective resource has been allocated before, + /// this would be the time to free/delete it. + /// The resources processed by this functions are only those that have been specifically allocated in order to return + /// them in one of the plugin functions to Mumble (e.g. the String returned by mumble_getName) and has nothing to do + /// with your plugin's internal resource management. + /// In short: Only resources passed from the plugin to Mumble via a return value may be processed by this function. + /// + /// NOTE1: This function may be called without the plugin being loaded + /// + /// NOTE2: that the pointer might be pointing to memory that had to be allocated without the plugin being loaded. + /// Therefore you should be very sure that there'll be another callback in which you want to free this memory, + /// should you decide to not do it here (which is hereby explicitly advised against). + /// + /// NOTE3: The pointer is const as Mumble won't mess with the memory allocated by the plugin (no modifications). + /// Nontheless this function is explicitly responsible for freeing the respective memory parts. If the memory has + /// been allocated using malloc(), it needs to be freed using free() which requires a const-cast. If however the + /// memory has been created using the new operator you have to cast the pointer back to its original type and then + /// use the delete operator on it (no const-cast necessary in this case). + /// See https://stackoverflow.com/questions/2819535/unable-to-free-const-pointers-in-c + /// and https://stackoverflow.com/questions/941832/is-it-safe-to-delete-a-void-pointer + /// + /// @param pointer The pointer to the memory that needs free-ing + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_releaseResource(const void* pointer); + + + + ////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////// GENERAL FUNCTIONS ////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + /// Tells the plugin some basic information about the Mumble client loading it. + /// This function will be the first one that is being called on this plugin - even before it is decided whether to load + /// the plugin at all. + /// + /// @param mumbleVersion The Version of the Mumble client + /// @param mumbleAPIVersion The Version of the plugin-API the Mumble client runs with + /// @param minimumExpectedAPIVersion The minimum Version the Mumble clients expects this plugin to meet in order to load + /// it + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_setMumbleInfo(mumble_version_t mumbleVersion, + mumble_version_t mumbleAPIVersion, + mumble_version_t minimumExpectedAPIVersion); + + /// Gets the Version of this plugin + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @returns The plugin's version + PLUGIN_EXPORT mumble_version_t PLUGIN_CALLING_CONVENTION mumble_getVersion(); + + /// Gets the name of the plugin author(s). + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @returns A String-wrapper containing the requested author name(s) + PLUGIN_EXPORT struct MumbleStringWrapper PLUGIN_CALLING_CONVENTION mumble_getAuthor(); + + /// Gets the description of the plugin. + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @returns A String-wrapper containing the requested description + PLUGIN_EXPORT struct MumbleStringWrapper PLUGIN_CALLING_CONVENTION mumble_getDescription(); + + /// Gets the feature set of this plugin. The feature set is described by bitwise or'ing the elements of the + /// Mumble_PluginFeature enum together. + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @returns The feature set of this plugin + PLUGIN_EXPORT uint32_t PLUGIN_CALLING_CONVENTION mumble_getFeatures(); + + /// Requests this plugin to deactivate the given (sub)set of provided features. + /// If this is not possible, the features that can't be deactivated shall be returned by this function. + /// + /// Example (check if FEATURE_POSITIONAL shall be deactivated): + /// @code + /// if (features & FEATURE_POSITIONAL) { + /// // positional shall be deactivated + /// }; + /// @endcode + /// + /// @param features The feature set that shall be deactivated + /// @returns The feature set that can't be disabled (bitwise or'ed). If all requested features can be disabled, return + /// FEATURE_NONE. If none of the requested features can be disabled return the unmodified features parameter. + PLUGIN_EXPORT uint32_t PLUGIN_CALLING_CONVENTION mumble_deactivateFeatures(uint32_t features); + + + + ////////////////////////////////////////////////////////////////////////////////// + //////////////////////////// POSITIONAL DATA ///////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + // If this plugin wants to provide positional data, the mumble_initPositionalData, mumble_fetchPositionalData + // and mumble_shutdownPositionalData functions have to be implemented together (implementing only a subset + // will yield the same result as if no support for positional data was implemened). + + /// Indicates that Mumble wants to use this plugin to request positional data. Therefore it should check whether it is + /// currently able to do so and allocate memory that is needed for that process. As a parameter this function gets an + /// array of names and an array of PIDs. They are of same length and the PID at index i belongs to a program whose name + /// is listed at index i in the "name-array". + /// + /// @param programNames An array of pointers to the program names + /// @param programPIDs An array of the corresponding program PIDs + /// @param programCount The length of programNames and programPIDs + /// @returns The error code. If everything went fine PDEC_OK shall be returned. In that case Mumble will start + /// frequently calling fetchPositionalData. If this returns anything but PDEC_OK, Mumble will assume that the plugin is + /// (currently) uncapable of providing positional data. In this case this function must not have allocated any memory + /// that needs to be cleaned up later on. Depending on the returned error code, Mumble might try to call this function + /// again at some point. + PLUGIN_EXPORT uint8_t PLUGIN_CALLING_CONVENTION mumble_initPositionalData(const char* const* programNames, + const uint64_t* programPIDs, + size_t programCount); + + /// Retrieves the positional data. If no data can be fetched, set all float-vectors to 0 and return false. + /// + /// @param[out] avatarPos A float-array of size 3 representing the cartesian position of the player/avatar in the ingame + /// world. One unit represents one meter of distance. + /// @param[out] avatarDir A float-array of size 3 representing the cartesian direction-vector of the player/avatar + /// ingame (where it is facing). + /// @param[out] avatarAxis A float-array of size 3 representing the vector pointing from the toes of the character to + /// its head. One unit represents one meter of distance. + /// @param[out] cameraPos A float-array of size 3 representing the cartesian position of the camera in the ingame world. + /// One unit represents one meter of distance. + /// @param[out] cameraDir A float-array of size 3 representing the cartesian direction-vector of the camera ingame + /// (where it is facing). + /// @param[out] cameraAxis A float-array of size 3 representing a vector from the bottom of the camera to its top. One + /// unit represents one meter of distance. + /// @param[out] context A pointer to where the pointer to a C-encoded string storing the context of the provided + /// positional data shall be written. This context should include information about the server (and team) the player is + /// on. Only players with identical context will be able to hear each other's audio. The returned pointer has to remain + /// valid until the next invokation of this function or until shutdownPositionalData is called. + /// @param[out] identity A pointer to where the pointer to a C-encoded string storing the identity of the player shall + /// be written. It can be polled by external scripts from the server and should uniquely identify the player in the + /// game. The pointer has to remain valid until the next invokation of this function or until shutdownPositionalData is + /// called. + /// @returns Whether this plugin can continue delivering positional data. If this function returns false, + /// shutdownPositionalData will be called. + PLUGIN_EXPORT bool PLUGIN_CALLING_CONVENTION mumble_fetchPositionalData(float* avatarPos, float* avatarDir, + float* avatarAxis, float* cameraPos, + float* cameraDir, float* cameraAxis, + const char** context, const char** identity); + + /// Indicates that this plugin will not be asked for positional data any longer. Thus any memory allocated for this + /// purpose should be freed at this point. + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_shutdownPositionalData(); + + /// The context in positional data is used to determine whether different positional data sets from different + /// clients belong to the same game (and same server). Only if the contexts matches up across these clients, + /// will Mumble activate the positional audio effects, as it will assume that these clients are playing the + /// same game together. + /// The context is set during fetching of the other positional data and is usually something like e.g. the + /// current server's name. In order to avoid clashes between different plugins (that most likely work for + /// different games), the context is prefixed by Mumble. If this function is not implemented, the name of + /// the plugin is used as a prefix (which tends to be the supported game's name), but sometimes a different + /// prefix is desirable. For these cases, a custom prefix can be provided through this function. + /// + /// NOTE that while it is possible to allocate a string for this purpose every time this function is called + /// and then letting mumble release the resource again (via mumble_releaseResource), it is generally not the + /// advised way of doing things (it may impact overall performance negatively, since this function will be + /// called very frequently). Instead you should either return a static string (if your language supports that + /// and if it actually fits your needs) or you should allocate a string during mumble_initPositionalData and + /// free it in mumble_shutdownPositionalData and when returning the string in this function, tell Mumble that + /// the string does not need releasing. + /// + /// @returns The context prefix to use for positional data fetched by this plugin. + /// + /// @since Plugin API v1.1.0 + PLUGIN_EXPORT struct MumbleStringWrapper PLUGIN_CALLING_CONVENTION mumble_getPositionalDataContextPrefix(); + + + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////////// EVENTHANDLERS / CALLBACK FUNCTIONS //////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + /// Called when connecting to a server. + /// Note that in most cases you'll want to use mumble_onServerSynchronized instead. + /// Note also that this callback will be called from a DIFFERENT THREAD! + /// + /// @param connection The ID of the newly established server-connection + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onServerConnected(mumble_connection_t connection); + + /// Called when disconnecting from a server. + /// Note that this callback is called from a DIFFERENT THREAD! + /// + /// @param connection The ID of the server-connection that has been terminated + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onServerDisconnected(mumble_connection_t connection); + + /// Called when the client has finished synchronizing with the server + /// + /// @param connection The ID of the server-connection that has been terminated + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onServerSynchronized(mumble_connection_t connection); + + /// Called whenever any user on the server enters a channel + /// This function will also be called when freshly connecting to a server as each user on that + /// server needs to be "added" to the respective channel as far as the local client is concerned. + /// + /// @param connection The ID of the server-connection this event is connected to + /// @param userID The ID of the user this event has been triggered for + /// @param previousChannelID The ID of the chanel the user is coming from. Negative IDs indicate that there is no + /// previous channel (e.g. the user freshly connected to the server) or the channel isn't available because of any other + /// reason. + /// @param newChannelID The ID of the channel the user has entered. If the ID is negative, the new channel could not be + /// retrieved. This means that the ID is invalid. + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onChannelEntered(mumble_connection_t connection, + mumble_userid_t userID, + mumble_channelid_t previousChannelID, + mumble_channelid_t newChannelID); + + /// Called whenever a user leaves a channel. + /// This includes a client disconnecting from the server as this will also lead to the user not being in that channel + /// anymore. + /// + /// @param connection The ID of the server-connection this event is connected to + /// @param userID The ID of the user that left the channel + /// @param channelID The ID of the channel the user left. If the ID is negative, the channel could not be retrieved. + /// This means that the ID is invalid. + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onChannelExited(mumble_connection_t connection, + mumble_userid_t userID, + mumble_channelid_t channelID); + + /// Called when any user changes his/her talking state. + /// + /// @param connection The ID of the server-connection this event is connected to + /// @param userID The ID of the user whose talking state has been changed + /// @param talkingState The new TalkingState the user has switched to. + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onUserTalkingStateChanged(mumble_connection_t connection, + mumble_userid_t userID, + mumble_talking_state_t talkingState); + + /// Called whenever there is audio input. + /// Note that this callback will be called from the AUDIO THREAD. + /// Note also that blocking this callback will cause Mumble's audio processing to get suspended. + /// + /// @param inputPCM A pointer to a short-array holding the pulse-code-modulation (PCM) representing the audio input. Its + /// length is sampleCount * channelCount. The PCM format for stereo input is [LRLRLR...] where L and R are samples of + /// the left and right channel respectively. + /// @param sampleCount The amount of sample points per channel + /// @param channelCount The amount of channels in the audio + /// @param sampleRate The used sample rate in Hz + /// @param isSpeech A boolean flag indicating whether Mumble considers the input as part of speech (instead of + /// background noise) + /// @returns Whether this callback has modified the audio input-array + PLUGIN_EXPORT bool PLUGIN_CALLING_CONVENTION mumble_onAudioInput(short* inputPCM, uint32_t sampleCount, + uint16_t channelCount, uint32_t sampleRate, + bool isSpeech); + + /// Called whenever Mumble fetches data from an active audio source (could be a voice packet or a playing sample). + /// The provided audio buffer is the raw buffer without any processing applied to it yet. + /// Note that this callback will be called from the AUDIO THREAD. + /// Note also that blocking this callback will cause Mumble's audio processing to get suspended. + /// + /// @param outputPCM A pointer to a float-array holding the pulse-code-modulation (PCM) representing the audio output. + /// Its length is sampleCount * channelCount. The PCM format for stereo output is [LRLRLR...] where L and R are samples + /// of the left and right channel respectively. + /// @param sampleCount The amount of sample points per channel + /// @param channelCount The amount of channels in the audio + /// @param sampleRate The used sample rate in Hz + /// @param isSpeech Whether this audio belongs to a received voice packet (and will thus (most likely) contain speech) + /// @param userID If isSpeech is true, this contains the ID of the user this voice packet belongs to. If isSpeech is + /// false, the content of this parameter is unspecified and should not be accessed + /// @returns Whether this callback has modified the audio output-array + PLUGIN_EXPORT bool PLUGIN_CALLING_CONVENTION mumble_onAudioSourceFetched(float* outputPCM, uint32_t sampleCount, + uint16_t channelCount, uint32_t sampleRate, + bool isSpeech, mumble_userid_t userID); + + /// Called whenever the fully mixed and processed audio is about to be handed to the audio backend (about to be played). + /// Note that this happens immediately before Mumble clips the audio buffer. + /// Note that this callback will be called from the AUDIO THREAD. + /// Note also that blocking this callback will cause Mumble's audio processing to get suspended. + /// + /// @param outputPCM A pointer to a float-array holding the pulse-code-modulation (PCM) representing the audio output. + /// Its length is sampleCount * channelCount. The PCM format for stereo output is [LRLRLR...] where L and R are samples + /// of the left and right channel respectively. + /// @param sampleCount The amount of sample points per channel + /// @param channelCount The amount of channels in the audio + /// @param sampleRate The used sample rate in Hz + /// @returns Whether this callback has modified the audio output-array + PLUGIN_EXPORT bool PLUGIN_CALLING_CONVENTION mumble_onAudioOutputAboutToPlay(float* outputPCM, uint32_t sampleCount, + uint16_t channelCount, + uint32_t sampleRate); + + /// Called whenever data has been received that has been sent by a plugin. This data should only be processed by the + /// intended plugin. For this reason a dataID is provided that should be used to determine whether the data is intended + /// for this plugin or not. As soon as the data has been processed, no further plugins will be notified about it. + /// + /// @param connection The ID of the server-connection the data is coming from + /// @param sender The ID of the user whose client's plugin has sent the data + /// @param data The sent data array. This can be an arbitrary sequence of bytes. + /// @param dataLength The length of the data array + /// @param dataID The ID of this data (C-encoded) + /// @return Whether the given data has been processed by this plugin + PLUGIN_EXPORT bool PLUGIN_CALLING_CONVENTION mumble_onReceiveData(mumble_connection_t connection, + mumble_userid_t sender, const uint8_t* data, + size_t dataLength, const char* dataID); + + /// Called when a new user gets added to the user model. This is the case when that new user freshly connects to the + /// server the local user is on but also when the local user connects to a server other clients are already connected to + /// (in this case this method will be called for every client already on that server). + /// + /// @param connection An object used to identify the current connection + /// @param userID The ID of the user that has been added + + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onUserAdded(mumble_connection_t connection, mumble_userid_t userID); + + /// Called when a user gets removed from the user model. This is the case when that user disconnects from the server the + /// local user is on but also when the local user disconnects from a server other clients are connected to (in this case + /// this method will be called for every client on that server). + /// + /// @param connection An object used to identify the current connection + /// @param userID The ID of the user that has been removed + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onUserRemoved(mumble_connection_t connection, + mumble_userid_t userID); + + /// Called when a new channel gets added to the user model. This is the case when a new channel is created on the server + /// the local user is on but also when the local user connects to a server that contains channels other than the + /// root-channel (in this case this method will be called for ever non-root channel on that server). + /// + /// @param connection An object used to identify the current connection + /// @param channelID The ID of the channel that has been added + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onChannelAdded(mumble_connection_t connection, + mumble_channelid_t channelID); + + /// Called when a channel gets removed from the user model. This is the case when a channel is removed on the server the + /// local user is on but also when the local user disconnects from a server that contains channels other than the + /// root-channel (in this case this method will be called for ever non-root channel on that server). + /// + /// @param connection An object used to identify the current connection + /// @param channelID The ID of the channel that has been removed + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onChannelRemoved(mumble_connection_t connection, + mumble_channelid_t channelID); + + /// Called when a channel gets renamed. This also applies when a new channel is created (thus assigning it an initial + /// name is also considered renaming). + /// + /// @param connection An object used to identify the current connection + /// @param channelID The ID of the channel that has been renamed + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onChannelRenamed(mumble_connection_t connection, + mumble_channelid_t channelID); + + /// Called when a key has been pressed or released while Mumble has keyboard focus. + /// Note that this callback will only work if the user has explicitly given permission to monitor keyboard + /// events for this plugin. Thus if you want to use this callback, make sure your users know that they have to + /// enable that. + /// + /// @param keyCode The key code of the respective key. The character codes are defined + /// via the Mumble_KeyCode enum. For printable 7-bit ASCII characters these codes conform + /// to the ASCII code-page with the only difference that case is not distinguished. Therefore + /// always the upper-case letter code will be used for letters. + /// @param wasPres Whether the respective key has been pressed (instead of released) + PLUGIN_EXPORT void PLUGIN_CALLING_CONVENTION mumble_onKeyEvent(uint32_t keyCode, bool wasPress); + + + + ////////////////////////////////////////////////////////////////////////////////// + ////////////////////////////// PLUGIN UPDATES //////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + + /// This function is used to determine whether the plugin can find an update for itself that is available for download. + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @return Whether the plugin was able to find an update for itself + PLUGIN_EXPORT bool PLUGIN_CALLING_CONVENTION mumble_hasUpdate(); + + /// This function is used to retrieve the URL for downloading the newer/updated version of this plugin. + /// + /// NOTE: This function may be called without the plugin being loaded + /// + /// @returns A String-wrapper containing the requested URL + PLUGIN_EXPORT struct MumbleStringWrapper PLUGIN_CALLING_CONVENTION mumble_getUpdateDownloadURL(); + + + + ////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////// DEFAULT FUNCTIONS ////////////////////////////////// + ////////////////////////////////////////////////////////////////////////////////// + // These functions don't have to be implemented by you + PLUGIN_EXPORT mumble_version_t mumble_getPluginFunctionsVersion(); + +#ifndef MUMBLE_PLUGIN_NO_DEFAULT_FUNCTION_DEFINITIONS + + PLUGIN_EXPORT mumble_version_t mumble_getPluginFunctionsVersion() { + return MUMBLE_PLUGIN_FUNCTIONS_VERSION; + } + +#endif + +#ifdef __cplusplus +} +#endif + + +#endif diff --git a/extensions/src/ACRE2Mumble/mumble_includes/PluginComponents_v_1_0_x.h b/extensions/src/ACRE2Mumble/mumble_includes/PluginComponents_v_1_0_x.h new file mode 100644 index 000000000..99f00d5b3 --- /dev/null +++ b/extensions/src/ACRE2Mumble/mumble_includes/PluginComponents_v_1_0_x.h @@ -0,0 +1,416 @@ +// Copyright 2021 The Mumble Developers. All rights reserved. +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file at the root of the +// Mumble source tree or at . + +/// This header file contains definitions of types and other components used in Mumble's plugin system + +#ifndef EXTERNAL_MUMBLE_PLUGINCOMPONENT_H_ +#define EXTERNAL_MUMBLE_PLUGINCOMPONENT_H_ + +#include +#include +#include + +#ifdef __cplusplus +# include +#endif + +#if defined(QT_CORE_LIB) || defined(QT_VERSION) +# include +#endif + +// define the calling convention macro based on the compiler being used +#if defined(_MSC_VER) +# define PLUGIN_CALLING_CONVENTION __cdecl +#elif defined(__MINGW32__) +# define PLUGIN_CALLING_CONVENTION __attribute__((cdecl)) +#else +# define PLUGIN_CALLING_CONVENTION +#endif + + +/// This enum's values correspond to special feature sets a plugin may provide. +/// They are meant to be or'ed together to represent the total feature set of a plugin. +enum Mumble_PluginFeature { + /// None of the below + MUMBLE_FEATURE_NONE = 0, + /// The plugin provides positional data from a game + MUMBLE_FEATURE_POSITIONAL = 1 << 0, + /// The plugin modifies the input/output audio itself + MUMBLE_FEATURE_AUDIO = 1 << 1 +}; + +/// This enum's values represent talking states a user can be in when using Mumble. +enum Mumble_TalkingState { + MUMBLE_TS_INVALID = -1, + MUMBLE_TS_PASSIVE = 0, + MUMBLE_TS_TALKING, + MUMBLE_TS_WHISPERING, + MUMBLE_TS_SHOUTING, + MUMBLE_TS_TALKING_MUTED +}; + +/// This enum's values represent transmission modes a user might have configured. Transmission mode +/// in this context is referring to a method that determines when a user is speaking and thus when +/// to transmit audio packets. +enum Mumble_TransmissionMode { MUMBLE_TM_CONTINOUS, MUMBLE_TM_VOICE_ACTIVATION, MUMBLE_TM_PUSH_TO_TALK }; + +/// This enum's values represent the error codes that are being used by the MumbleAPI. +/// You can get a string-representation for each error code via the errorMessage function. +enum Mumble_ErrorCode { + MUMBLE_EC_INTERNAL_ERROR = -2, + MUMBLE_EC_GENERIC_ERROR = -1, + MUMBLE_EC_OK = 0, + MUMBLE_EC_POINTER_NOT_FOUND, + MUMBLE_EC_NO_ACTIVE_CONNECTION, + MUMBLE_EC_USER_NOT_FOUND, + MUMBLE_EC_CHANNEL_NOT_FOUND, + MUMBLE_EC_CONNECTION_NOT_FOUND, + MUMBLE_EC_UNKNOWN_TRANSMISSION_MODE, + MUMBLE_EC_AUDIO_NOT_AVAILABLE, + MUMBLE_EC_INVALID_SAMPLE, + MUMBLE_EC_INVALID_PLUGIN_ID, + MUMBLE_EC_INVALID_MUTE_TARGET, + MUMBLE_EC_CONNECTION_UNSYNCHRONIZED, + MUMBLE_EC_INVALID_API_VERSION, + MUMBLE_EC_UNSYNCHRONIZED_BLOB, + MUMBLE_EC_UNKNOWN_SETTINGS_KEY, + MUMBLE_EC_WRONG_SETTINGS_TYPE, + MUMBLE_EC_SETTING_WAS_REMOVED, + MUMBLE_EC_DATA_TOO_BIG, + MUMBLE_EC_DATA_ID_TOO_LONG, + MUMBLE_EC_API_REQUEST_TIMEOUT, + MUMBLE_EC_OPERATION_UNSUPPORTED_BY_SERVER, +}; + +/// This enum's values represent error codes specific to the framework of handling positional data +/// gathering (needed for Mumble's positional audio feature). +enum Mumble_PositionalDataErrorCode { + /// Positional data has been initialized properly + MUMBLE_PDEC_OK = 0, + /// Positional data is temporarily unavailable (e.g. because the corresponding process isn't running) but might be + /// at another point in time. + MUMBLE_PDEC_ERROR_TEMP, + /// Positional data is permanently unavailable (e.g. because the respective memory offsets are outdated). + MUMBLE_PDEC_ERROR_PERM +}; + +/// This enum's values represent keys for specific settings inside Mumble. +enum Mumble_SettingsKey { + MUMBLE_SK_INVALID = -1, + MUMBLE_SK_AUDIO_INPUT_VOICE_HOLD = 0, + MUMBLE_SK_AUDIO_INPUT_VAD_SILENCE_THRESHOLD = 1, + MUMBLE_SK_AUDIO_INPUT_VAD_SPEECH_THRESHOLD = 2, + MUMBLE_SK_AUDIO_OUTPUT_PA_MINIMUM_DISTANCE = 3, + MUMBLE_SK_AUDIO_OUTPUT_PA_MAXIMUM_DISTANCE = 4, + MUMBLE_SK_AUDIO_OUTPUT_PA_BLOOM = 5, + MUMBLE_SK_AUDIO_OUTPUT_PA_MINIMUM_VOLUME = 6, +}; + +/// This enum's values represent the key-codes Mumble's API uses to reference keys on the keyboard. +enum Mumble_KeyCode { + MUMBLE_KC_INVALID = -1, + + // Non-printable characters first + MUMBLE_KC_NULL = 0, + MUMBLE_KC_END = 1, + MUMBLE_KC_LEFT = 2, + MUMBLE_KC_RIGHT = 4, + MUMBLE_KC_UP = 5, + MUMBLE_KC_DOWN = 6, + MUMBLE_KC_DELETE = 7, + MUMBLE_KC_BACKSPACE = 8, + MUMBLE_KC_TAB = 9, + MUMBLE_KC_ENTER = 10, // == '\n' + MUMBLE_KC_ESCAPE = 27, + MUMBLE_KC_PAGE_UP = 11, + MUMBLE_KC_PAGE_DOWN = 12, + MUMBLE_KC_SHIFT = 13, + MUMBLE_KC_CONTROL = 14, + MUMBLE_KC_META = 15, + MUMBLE_KC_ALT = 16, + MUMBLE_KC_ALT_GR = 17, + MUMBLE_KC_CAPSLOCK = 18, + MUMBLE_KC_NUMLOCK = 19, + MUMBLE_KC_SUPER = 20, // == windows key + MUMBLE_KC_HOME = 21, // == Pos1 + MUMBLE_KC_PRINT = 22, + MUMBLE_KC_SCROLLLOCK = 23, + + // Printable characters are assigned to their ASCII code + MUMBLE_KC_SPACE = ' ', + MUMBLE_KC_EXCLAMATION_MARK = '!', + MUMBLE_KC_DOUBLE_QUOTE = '"', + MUMBLE_KC_HASHTAG = '#', + MUMBLE_KC_DOLLAR = '$', + MUMBLE_KC_PERCENT = '%', + MUMBLE_KC_AMPERSAND = '&', + MUMBLE_KC_SINGLE_QUOTE = '\'', + MUMBLE_KC_OPEN_PARENTHESIS = '(', + MUMBLE_KC_CLOSE_PARENTHESIS = ')', + MUMBLE_KC_ASTERISK = '*', + MUMBLE_KC_PLUS = '+', + MUMBLE_KC_COMMA = ',', + MUMBLE_KC_MINUS = '-', + MUMBLE_KC_PERIOD = '.', + MUMBLE_KC_SLASH = '/', + MUMBLE_KC_0 = '0', + MUMBLE_KC_1 = '1', + MUMBLE_KC_2 = '2', + MUMBLE_KC_3 = '3', + MUMBLE_KC_4 = '4', + MUMBLE_KC_5 = '5', + MUMBLE_KC_6 = '6', + MUMBLE_KC_7 = '7', + MUMBLE_KC_8 = '8', + MUMBLE_KC_9 = '9', + MUMBLE_KC_COLON = ':', + MUMBLE_KC_SEMICOLON = ';', + MUMBLE_KC_LESS_THAN = '<', + MUMBLE_KC_EQUALS = '=', + MUMBLE_KC_GREATER_THAN = '>', + MUMBLE_KC_QUESTION_MARK = '?', + MUMBLE_KC_AT_SYMBOL = '@', + MUMBLE_KC_A = 'A', + MUMBLE_KC_B = 'B', + MUMBLE_KC_C = 'C', + MUMBLE_KC_D = 'D', + MUMBLE_KC_E = 'E', + MUMBLE_KC_F = 'F', + MUMBLE_KC_G = 'G', + MUMBLE_KC_H = 'H', + MUMBLE_KC_I = 'I', + MUMBLE_KC_J = 'J', + MUMBLE_KC_K = 'K', + MUMBLE_KC_L = 'L', + MUMBLE_KC_M = 'M', + MUMBLE_KC_N = 'N', + MUMBLE_KC_O = 'O', + MUMBLE_KC_P = 'P', + MUMBLE_KC_Q = 'Q', + MUMBLE_KC_R = 'R', + MUMBLE_KC_S = 'S', + MUMBLE_KC_T = 'T', + MUMBLE_KC_U = 'U', + MUMBLE_KC_V = 'V', + MUMBLE_KC_W = 'W', + MUMBLE_KC_X = 'X', + MUMBLE_KC_Y = 'Y', + MUMBLE_KC_Z = 'Z', + // leave out lowercase letters (for now) + MUMBLE_KC_OPEN_BRACKET = '[', + MUMBLE_KC_BACKSLASH = '\\', + MUMBLE_KC_CLOSE_BRACKET = ']', + MUMBLE_KC_CIRCUMFLEX = '^', + MUMBLE_KC_UNDERSCORE = '_', + MUMBLE_KC_GRAVE_AKCENT = '`', + MUMBLE_KC_OPEN_BRACE = '{', + MUMBLE_KC_VERTICAL_BAR = '|', + MUMBLE_KC_CLOSE_BRACE = '}', + MUMBLE_KC_TILDE = '~', + + // Some characters from the extended ASCII code + MUMBLE_KC_DEGREE_SIGN = 176, + + + + // F-keys + // Start at a value of 256 as extended ASCII codes range up to 255 + MUMBLE_KC_F1 = 256, + MUMBLE_KC_F2 = 257, + MUMBLE_KC_F3 = 258, + MUMBLE_KC_F4 = 259, + MUMBLE_KC_F5 = 260, + MUMBLE_KC_F6 = 261, + MUMBLE_KC_F7 = 262, + MUMBLE_KC_F8 = 263, + MUMBLE_KC_F9 = 264, + MUMBLE_KC_F10 = 265, + MUMBLE_KC_F11 = 266, + MUMBLE_KC_F12 = 267, + MUMBLE_KC_F13 = 268, + MUMBLE_KC_F14 = 269, + MUMBLE_KC_F15 = 270, + MUMBLE_KC_F16 = 271, + MUMBLE_KC_F17 = 272, + MUMBLE_KC_F18 = 273, + MUMBLE_KC_F19 = 274, +}; + +/// A struct for representing a version of the form major.minor.patch +struct MumbleVersion { + int32_t major; + int32_t minor; + int32_t patch; +#ifdef __cplusplus + bool operator<(const MumbleVersion& other) const { + if (this->major != other.major) { + return this->major < other.major; + } + if (this->minor != other.minor) { + return this->minor < other.minor; + } + // Major and Minor are equal + return this->patch < other.patch; + } + + bool operator>(const MumbleVersion& other) const { + if (this->major != other.major) { + return this->major > other.major; + } + if (this->minor != other.minor) { + return this->minor > other.minor; + } + // Major and Minor are equal + return this->patch > other.patch; + } + + bool operator>=(const MumbleVersion& other) const { + if (this->major != other.major) { + return this->major > other.major; + } + if (this->minor != other.minor) { + return this->minor > other.minor; + } + // Major and Minor are equal + return this->patch >= other.patch; + } + + bool operator<=(const MumbleVersion& other) const { + if (this->major != other.major) { + return this->major < other.major; + } + if (this->minor != other.minor) { + return this->minor < other.minor; + } + // Major and Minor are equal + return this->patch <= other.patch; + } + + bool operator==(const MumbleVersion& other) const { + return this->major == other.major && this->minor == other.minor && this->patch == other.patch; + } + + bool operator!=(const MumbleVersion& other) const { + return this->major != other.major || this->minor != other.minor || this->patch != other.patch; + } + + operator std::string() const { + return std::string("v") + std::to_string(this->major) + std::string(".") + std::to_string(this->minor) + + std::string(".") + std::to_string(this->patch); + } + +# if defined(QT_CORE_LIB) || defined(QT_VERSION) + operator QString() const { + return QString::fromLatin1("v%0.%1.%2").arg(this->major).arg(this->minor).arg(this->patch); + } +# endif +#endif +}; + +/// Obtains a String representation for the given numeric error code. +/// Note that the exact String representation corresponding to an error code may change and is thus +/// not part of the plugin API as such. This function acts merely as a convenience helper for printing +/// errors in a meaningful way. +/// +/// @param errorCode The error code to get the String representation for +/// @returns The error message coresponding to the given error code. The message +/// is encoded as a C-string and is static, meaning that it is safe to use the +/// returned pointer in your code. +inline const char* mumble_errorMessage(int16_t errorCode) { + switch (errorCode) { + case MUMBLE_EC_GENERIC_ERROR: + return "Generic error"; + case MUMBLE_EC_OK: + return "Ok - this is not an error"; + case MUMBLE_EC_POINTER_NOT_FOUND: + return "Can't find the passed pointer"; + case MUMBLE_EC_NO_ACTIVE_CONNECTION: + return "There is currently no active connection to a server"; + case MUMBLE_EC_USER_NOT_FOUND: + return "Can't find the requested user"; + case MUMBLE_EC_CHANNEL_NOT_FOUND: + return "Can't find the requested channel"; + case MUMBLE_EC_CONNECTION_NOT_FOUND: + return "Can't identify the requested connection"; + case MUMBLE_EC_UNKNOWN_TRANSMISSION_MODE: + return "Unknown transmission mode encountered"; + case MUMBLE_EC_AUDIO_NOT_AVAILABLE: + return "There is currently no audio output available"; + case MUMBLE_EC_INVALID_SAMPLE: + return "Attempted to use invalid sample (can't play it)"; + case MUMBLE_EC_INVALID_PLUGIN_ID: + return "Used an invalid plugin ID"; + case MUMBLE_EC_INVALID_MUTE_TARGET: + return "Used an invalid mute-target"; + case MUMBLE_EC_CONNECTION_UNSYNCHRONIZED: + return "The requested server connection has not yet finished synchronizing"; + case MUMBLE_EC_INVALID_API_VERSION: + return "The used API version is invalid or not supported"; + case MUMBLE_EC_UNSYNCHRONIZED_BLOB: + return "The requested blob (content) has not yet been synchronized between the client and the server"; + case MUMBLE_EC_UNKNOWN_SETTINGS_KEY: + return "The used settings-key does not match any key known to Mumble"; + case MUMBLE_EC_WRONG_SETTINGS_TYPE: + return "The referenced setting has a different type than requested"; + case MUMBLE_EC_SETTING_WAS_REMOVED: + return "The referenced setting got removed from Mumble and is no longer used"; + case MUMBLE_EC_DATA_TOO_BIG: + return "The given data is too large (exceeds limit)"; + case MUMBLE_EC_DATA_ID_TOO_LONG: + return "The given data ID is too long (exceeds limit)"; + case MUMBLE_EC_API_REQUEST_TIMEOUT: + return "A blocking API call took too long and was this aborted (probably preventing a deadlock)"; + case MUMBLE_EC_OPERATION_UNSUPPORTED_BY_SERVER: + return "The requested API operation depends on server-side functionality, not supported by the server " + "you're connected to"; + default: + return "Unknown error code"; + } +} + + +/// This struct is used to return Strings from a plugin to Mumble. It is needed in order to +/// work around the limitation of std::string not being part of C (it holds important information +/// about the String's lifetime management requirements). +struct MumbleStringWrapper { + /// The pointer to the actual String data + const char* data; + /// The size of the pointed String data + size_t size; + /// Whether the wrapped String needs to be released + /// after its usage. Instances for which this would be + /// false: Static Strings, String literals + bool needsReleasing; +}; + +/// Typedef for the type of a talking state +typedef enum Mumble_TalkingState mumble_talking_state_t; +/// Typedef for the type of a transmission mode +typedef enum Mumble_TransmissionMode mumble_transmission_mode_t; +/// Typedef for the type of a version +typedef struct MumbleVersion mumble_version_t; +/// Typedef for the type of a connection +typedef int32_t mumble_connection_t; +/// Typedef for the type of a user +typedef uint32_t mumble_userid_t; +/// Typedef for the type of a channel +typedef int32_t mumble_channelid_t; +/// Typedef for the type of an error (code) +typedef enum Mumble_ErrorCode mumble_error_t; +/// Typedef for the type of a plugin ID +typedef uint32_t mumble_plugin_id_t; +/// Typedef for the type of a key to a setting in Mumble +typedef enum Mumble_SettingsKey mumble_settings_key_t; +/// Typedef for the type of a key-code +typedef enum Mumble_KeyCode mumble_keycode_t; + + + +/// A constant holding the exit status of a successful operation +static const mumble_error_t MUMBLE_STATUS_OK = MUMBLE_EC_OK; +/// A constant holding the version object that is considered to correspond to an unknown version +static const mumble_version_t MUMBLE_VERSION_UNKNOWN = { 0, 0, 0 }; + +#endif // EXTERNAL_MUMBLE_PLUGINCOMPONENT_H_ diff --git a/extensions/src/ACRE2Shared/IClient.h b/extensions/src/ACRE2Shared/IClient.h index ceecf2418..5f6c623a0 100644 --- a/extensions/src/ACRE2Shared/IClient.h +++ b/extensions/src/ACRE2Shared/IClient.h @@ -1,49 +1,190 @@ #pragma once +#include "ACRE_VECTOR.h" +#include "Macros.h" +#include "Types.h" #include "compat.h" -#include "Types.h" -#include "Macros.h" -#include "ACRE_VECTOR.h" +#include #include +#include +#include #include #include class IClient { public: - virtual acre::Result initialize( void ) = 0; + IClient() noexcept = default; + virtual ~IClient() noexcept = default; + + virtual acre::Result initialize(void) = 0; - virtual acre::Result setMuted(const acre::id_t id, const bool muted) = 0; + virtual acre::Result setMuted(const acre::id_t id, const bool muted) = 0; virtual acre::Result setMuted(std::list idList, const bool muted) = 0; virtual acre::Result getMuted(const acre::id_t id) = 0; - virtual acre::Result stop() = 0; + virtual acre::Result stop() = 0; virtual acre::Result start(const acre::id_t id) = 0; virtual acre::Result enableMicrophone(const bool status) = 0; virtual acre::Result microphoneOpen(const bool status) = 0; - virtual acre::Result localStartSpeaking(const acre::Speaking speakingType) = 0; + virtual acre::Result localStartSpeaking(const acre::Speaking speakingType) = 0; virtual acre::Result localStartSpeaking(const acre::Speaking speakingType, const std::string radioId) = 0; - virtual acre::Result localStopSpeaking(const acre::Speaking speakingType) = 0; + virtual acre::Result localStopSpeaking(const acre::Speaking speakingType) = 0; - virtual std::string getTempFilePath( void ) = 0; + virtual std::string getTempFilePath(void) = 0; virtual std::string getConfigFilePath(void) = 0; virtual std::string getUniqueId() = 0; virtual acre::Result playSound(std::string path, acre::vec3_fp32_t position, const float32_t volume, const int32_t looping) = 0; - virtual acre::Result unMuteAll( void ) = 0; + virtual acre::Result unMuteAll(void) = 0; + + virtual acre::Result moveToServerChannel() = 0; + virtual acre::Result moveToPreviousChannel() = 0; + virtual acre::Result updateChannelDetails(const std::vector details) = 0; + virtual acre::Result updateShouldSwitchChannel(const bool state) = 0; + virtual bool shouldSwitchChannel() = 0; + virtual bool getVAD() = 0; + + virtual std::uint64_t findChannelByNames(std::vector details_) = 0; + + bool gethadVAD() const noexcept { return had_vad; } + void sethadVAD(const bool value_) noexcept { had_vad = value_; } + + bool getInputActive() const noexcept { return input_active; } + void setInputActive(const bool value_) noexcept { input_active = value_; } + + bool getOnRadio() const noexcept { return on_radio; } + void setOnRadio(const bool value_) noexcept { on_radio = value_; } + + std::int32_t getSpeakingState() const noexcept { return speaking_state; } + void setSpeakingState(const std::int32_t value_) noexcept { speaking_state = value_; } + + bool getRadioPTTDown() const noexcept { return radioPTTDown; } + void setRadioPTTDown(const bool value_) noexcept { radioPTTDown = value_; } + + bool getIntercomPTTDown() const noexcept { return intercom_ptt_down; } + void setIntercomPTTDown(const bool value_) noexcept { intercom_ptt_down = value_; } + + bool getGodPTTDown() const noexcept { return god_ptt_down; } + void setGodPTTDown(const bool value_) noexcept { god_ptt_down = value_; } + + bool getZeusPTTDown() const noexcept { return zeus_ptt_down; } + void setZeusPTTDown(const bool value_) noexcept { zeus_ptt_down = value_; } + + bool getMainPTTDown() const noexcept { return main_ptt_down; } + void setMainPTTDown(const bool value_) noexcept { main_ptt_down = value_; } + + bool getDirectFirst() const noexcept { return direct_first; } + void setDirectFirst(const bool value_) noexcept { direct_first = value_; } + + bool getHitTSSpeakingEvent() const noexcept { return hit_speaking_event; } + void setHitTSSpeakingEvent(const bool value_) noexcept { hit_speaking_event = value_; } + + bool getIsX3DInitialized() const noexcept { return x3d_initialized; } + + void setIsX3DInitialized(const bool value_) noexcept { x3d_initialized = value_; } + + std::uint32_t getSpeakerMask() const noexcept { return speaker_mask; } + void setSpeakerMask(const std::uint32_t value_) noexcept { speaker_mask = value_; } + + virtual std::uint64_t getPreviousChannel() const noexcept { return previous_channel; } + virtual void setPreviousChannel(const std::uint64_t value_) noexcept { previous_channel = value_; } + + virtual const std::vector &getChannelDetails() const noexcept { return channel_details; } + virtual void setChannelDetails(const std::vector &value_) noexcept { channel_details = value_; } + + virtual bool getShouldSwitchChannel() const noexcept { return should_switch_channel; } + virtual void setShouldSwitchChannel(const bool value_) noexcept { should_switch_channel = value_; } + + acre::State getState() const noexcept { return state; } + void setState(acre::State value) noexcept { state = value; } + +protected: + virtual std::uint32_t getWordMatches(const std::string &string1_, const std::string &string2_) noexcept { + std::vector words1; + std::vector words2; + + std::string temp; + std::stringstream stringstream1(string1_); + + while (stringstream1 >> temp) { + words1.push_back(temp); + } + std::stringstream stringstream2(string2_); + while (stringstream2 >> temp) { + words2.push_back(temp); + } + + std::int32_t matches = 0; + for (auto &word1 : words1) { + for (auto &word2 : words2) { + if (word1 == word2) { + matches++; + } + } + } + return matches; + } + + virtual std::uint32_t levenshteinDistance(const std::string &string1_, const std::string &string2_) noexcept { + std::int32_t length1 = string1_.size(); + const std::int32_t length2 = string2_.size(); + + const decltype(length1) columnStart = decltype(length1)(1); + + decltype(length1) *const column = new decltype(length1)[length1 + 1]; + std::iota(column + columnStart, column + length1 + 1, columnStart); + + for (auto x = columnStart; x <= length2; x++) { + column[0] = x; + std::int32_t lastDiagonal = x - columnStart; + for (auto y = columnStart; y <= length1; y++) { + const int32_t oldDiagonal = column[y]; + const std::initializer_list possibilities = { + column[y] + 1, column[y - 1] + 1, lastDiagonal + (string1_[y - 1] == string2_[x - 1] ? 0 : 1)}; + column[y] = min(possibilities); + lastDiagonal = oldDiagonal; + } + } + const std::int32_t result = column[length1]; + delete[] column; + return result; + } + + virtual std::string removeSubstrings(std::string string_, std::string substring_) noexcept { + const std::string::size_type substringLength = substring_.length(); + for (auto iterator = string_.find(substring_); iterator != std::string::npos; iterator = string_.find(substring_)) + string_.erase(iterator, substringLength); + return string_; + } + +private: + bool had_vad = false; + bool input_active = false; + bool on_radio = false; + + std::int32_t speaking_state = 0; + + bool radioPTTDown = false; + bool intercom_ptt_down = false; + bool god_ptt_down = false; + bool zeus_ptt_down = false; + bool main_ptt_down = false; + bool direct_first = false; + bool hit_speaking_event = false; + bool x3d_initialized = false; + + std::uint32_t speaker_mask = 0U; + std::uint64_t previous_channel = 0LU; - virtual acre::Result moveToServerTS3Channel() = 0; - virtual acre::Result moveToPreviousTS3Channel() = 0; - virtual acre::Result updateTs3ChannelDetails(const std::vector details) = 0; - virtual acre::Result updateShouldSwitchTS3Channel(const bool state) = 0; - virtual bool shouldSwitchTS3Channel() = 0; + std::vector channel_details; + bool should_switch_channel = true; - virtual void setState(const acre::State value) = 0; - virtual acre::State getState() const = 0; + acre::State state = acre::State::stopped; }; diff --git a/extensions/src/ACRE2Shared/IServer.h b/extensions/src/ACRE2Shared/IServer.h index b3213e7f8..71aba4bde 100644 --- a/extensions/src/ACRE2Shared/IServer.h +++ b/extensions/src/ACRE2Shared/IServer.h @@ -9,13 +9,14 @@ class IServer { public: - virtual ~IServer(){} + virtual ~IServer() = default; virtual acre::Result initialize(void) = 0; virtual acre::Result shutdown(void) = 0; virtual acre::Result sendMessage(IMessage *const msg) = 0; virtual acre::Result handleMessage(unsigned char *const data) = 0; + virtual acre::Result handleMessage(unsigned char* msg, size_t length) = 0; virtual acre::Result release(void) = 0; diff --git a/extensions/src/ACRE2Shared/Lockable.h b/extensions/src/ACRE2Shared/Lockable.h index 3e74fdd24..dfe9d205c 100644 --- a/extensions/src/ACRE2Shared/Lockable.h +++ b/extensions/src/ACRE2Shared/Lockable.h @@ -7,11 +7,11 @@ class CLockable { private: std::recursive_mutex m_lockable_mutex; public: - void lock( void ) { + void lock() { m_lockable_mutex.lock(); }; - void unlock( void ) { + void unlock() { m_lockable_mutex.unlock(); }; }; diff --git a/extensions/src/ACRE2Shared/TextMessage.cpp b/extensions/src/ACRE2Shared/TextMessage.cpp index c1acaa490..9382ffb41 100644 --- a/extensions/src/ACRE2Shared/TextMessage.cpp +++ b/extensions/src/ACRE2Shared/TextMessage.cpp @@ -22,19 +22,7 @@ acre::Result CTextMessage::parse(char *const value, const size_t len) { return acre::Result::ok; } - // Check to make sure the entire chunk of data is a NULL terminated ascii string - for (size_t x = 0 ; x < len; x++) { - if (!__isascii(value[x]) && value[x] != 0x00) { - this->m_IsValid = false; - LOG("INVALID PACKET DETECTED l:%d", len); - return acre::Result::error; - } - if (value[x] == 0x00) { // null terminate, bail - break; - } - } - - const size_t length = strlen(value) + 1; + const size_t length = len + 1; if (length < 3) { this->m_IsValid = false; return acre::Result::error; diff --git a/extensions/src/ACRE2Shared/version.h b/extensions/src/ACRE2Shared/version.h index da6827751..c8c9079d0 100644 --- a/extensions/src/ACRE2Shared/version.h +++ b/extensions/src/ACRE2Shared/version.h @@ -1,4 +1,4 @@ #define ACRE_VERSION_MAJOR 2 #define ACRE_VERSION_MINOR 10 #define ACRE_VERSION_SUBMINOR 0 -#define ACRE_VERSION_BUILD 1050 +#define ACRE_VERSION_BUILD 1051 diff --git a/extensions/src/ACRE2Steam/CMakeLists.txt b/extensions/src/ACRE2Steam/CMakeLists.txt index eedfc3f0f..e6ce77e2c 100644 --- a/extensions/src/ACRE2Steam/CMakeLists.txt +++ b/extensions/src/ACRE2Steam/CMakeLists.txt @@ -9,7 +9,8 @@ file(GLOB_RECURSE SOURCES *.h *.hpp *.c *.cpp) add_library( ${ACRE_NAME} MODULE ${SOURCES} ${GLOBAL_SOURCES}) set_target_properties(${ACRE_NAME} PROPERTIES FOLDER ACRE2) - +target_compile_features(${ACRE_NAME} PRIVATE cxx_std_17) +#set_target_properties(${ACRE_NAME} PRIVATE LINKER_LANGUAGE CXX) # Copy and rename if(USE_64BIT_BUILD) diff --git a/extensions/src/ACRE2Steam/CallExt_DllMain.cpp b/extensions/src/ACRE2Steam/CallExt_DllMain.cpp index 6817ce0d4..af0a6c6a7 100644 --- a/extensions/src/ACRE2Steam/CallExt_DllMain.cpp +++ b/extensions/src/ACRE2Steam/CallExt_DllMain.cpp @@ -1,276 +1,48 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * @author Cliff Foster (Nou) + * @author James Smith (snippers) + * + * @copyright Copyright (c) 2020 International Development & Integration Systems LLC + * + * Voice over IP auto-plugin copy functionality. + */ + #include "compat.h" //#include "Log.h" #include "Macros.h" -#include -#include -#include -#include -#include -#include -#include "shlobj.h" #include "Shlwapi.h" +#include "command_options.hpp" +#include "mumble_plugin.hpp" +#include "shlobj.h" +#include "ts3_plugin.hpp" + #include -#include #include +#include +#include +#include +#include #include +#include +#include +#include #pragma comment(lib, "shlwapi.lib") -enum class SteamCommand : uint8_t { - Check, - GetPath, - DoCopy -}; +enum class SteamCommand : uint8_t { check, get_path, do_copy }; void ClosePipe(); extern "C" { - __declspec (dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize); - __declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function); +__declspec(dllexport) void __stdcall RVExtensionVersion(char *output, int outputSize); +__declspec(dllexport) void __stdcall RVExtension(char *output, int outputSize, const char *function); }; void __stdcall RVExtensionVersion(char *output, int outputSize) { sprintf_s(output, outputSize - 1, "%s", ACRE_VERSION); } -std::vector split(const std::string &s, char delim) { - std::vector elems; - std::stringstream ss(s); - std::string item; - while (std::getline(ss, item, delim)) { - elems.push_back(item); - } - return elems; -} - -inline std::string get_path() { - char moduleName[MAX_PATH]; - GetModuleFileNameA(nullptr, moduleName, MAX_PATH); - return std::string(moduleName); -} - -std::vector get_cmdline() { - std::vector cmdline(split(GetCommandLineA(), ' ')); - - const std::string par_opt("-par="); - std::string par_path; - for (auto const& option : cmdline) { - if (option.rfind(par_opt, 0) != std::string::npos) { - par_path = option.substr(par_opt.length()); - break; - } - } - - // Append all options from parameter file - if (!par_path.empty()) { - std::ifstream par_file(par_path); - - if (par_file.is_open()) { - std::string line; - - std::getline(par_file, line); - if (line == "class Arg") { // ignore old format - par_file.close(); - return cmdline; - } - - par_file.seekg(0, par_file.beg); - - while (std::getline(par_file, line)) { - cmdline.push_back(line); - } - - par_file.close(); - } - } - - return cmdline; -} - -inline std::string get_path(const std::string &filepath) { - char drive[_MAX_DRIVE]; - char dir [_MAX_DIR]; - - _splitpath( - filepath.c_str(), - drive, - dir, - nullptr, - nullptr - ); - - return (std::string(drive) + std::string(dir)); -} - -inline std::string get_quoted(std::string &text) { - std::string found_text = ""; - - std::string::size_type start_position = text.find("\""); - if (start_position != std::string::npos) { - ++start_position; // start after the double quotes. - // look for end position; - std::string::size_type end_position = text.find("\""); - if (end_position != std::string::npos) { - found_text = text.substr(start_position, end_position - start_position); - } - } - return found_text; -} -EXTERN_C IMAGE_DOS_HEADER __ImageBase; - -inline std::string find_mod_folder() { - char module_path[MAX_PATH]; - GetModuleFileNameA((HINSTANCE)&__ImageBase, module_path, MAX_PATH); - - char drive[_MAX_DRIVE]; - char dir[_MAX_DIR]; - - _splitpath( - module_path, - drive, - dir, - NULL, - NULL - ); - - return (std::string(drive) + std::string(dir)); -} - -inline std::string find_mod_file(const std::string &filename) { - std::string path = find_mod_folder() + filename; - if (!PathFileExistsA(path.c_str())) { - // No mod path was set, it means they used the mod config. It *DOES* mean it relative to a folder in our path at least. - // So, we just search all the local folders - - WIN32_FIND_DATAA data; - std::string path(""); - HANDLE hFile = FindFirstFileA(path.c_str(), &data); - - if (hFile == INVALID_HANDLE_VALUE) - return ""; - - while ((FindNextFile(hFile, &data) != 0) || (GetLastError() != ERROR_NO_MORE_FILES)) { - if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - const std::string fullpath = std::string(data.cFileName) + filename; - if (PathFileExistsA(fullpath.c_str())) { - path = fullpath; - break; - } - } - } - } - return path; -} - -std::string ReadRegValue(HKEY root, const std::string &key, const std::string &name) { - HKEY hkey; - if (RegOpenKeyExA(root, key.c_str(), 0, KEY_READ, &hkey) != ERROR_SUCCESS) { - return ""; - } - - DWORD type; - DWORD cbData; - if (RegQueryValueExA(hkey, name.c_str(), nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS) { - RegCloseKey(hkey); - return ""; - } - - if (type != REG_SZ) { - RegCloseKey(hkey); - return ""; - } - - std::string value(cbData / sizeof(char), '\0'); - if (RegQueryValueExA(hkey, name.c_str(), nullptr, nullptr, reinterpret_cast(&value[0]), &cbData) != ERROR_SUCCESS) { - RegCloseKey(hkey); - return ""; - } - - RegCloseKey(hkey); - - size_t firstNull = value.find_first_of('\0'); - if (firstNull != std::string::npos) { - value.resize(firstNull); - } - - return value; -} - -std::string ReadRegValue64(HKEY root, const std::string &key, const std::string &name) { - HKEY hkey; - if (RegOpenKeyExA(root, key.c_str(), 0, KEY_READ | KEY_WOW64_64KEY, &hkey) != ERROR_SUCCESS) - return ""; - - DWORD type; - DWORD cbData; - if (RegQueryValueExA(hkey, name.c_str(), nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS) { - RegCloseKey(hkey); - return ""; - } - - if (type != REG_SZ) { - RegCloseKey(hkey); - return ""; - } - - std::string value(cbData / sizeof(char), '\0'); - if (RegQueryValueExA(hkey, name.c_str(), nullptr, nullptr, reinterpret_cast(&value[0]), &cbData) != ERROR_SUCCESS) { - RegCloseKey(hkey); - return ""; - } - - RegCloseKey(hkey); - - size_t firstNull = value.find_first_of('\0'); - if (firstNull != std::string::npos) - value.resize(firstNull); - - return value; -} - -bool compare_file(const std::string &pathA, const std::string &pathB) { - - // Open both files ath the end to check their size - std::ifstream fileA(pathA, std::ifstream::ate | std::ifstream::binary); - std::ifstream fileB(pathB, std::ifstream::ate | std::ifstream::binary); - - if (fileA.tellg() != fileB.tellg()) { - return false; - } - - // Files are of the same size. Rewind and compare file contents - fileA.seekg(0); - fileB.seekg(0); - - std::istreambuf_iterator beginA(fileA); - std::istreambuf_iterator beginB(fileB); - - return std::equal(beginA, std::istreambuf_iterator(), beginB); -} - -bool skip_plugin_copy() { - for (auto const& option : get_cmdline()) { - if (option == "-skipAcrePluginCopy") { - return true; - } - } - - return false; -} - -void checkTsLocations(const std::string &appData, const std::string &rootkey, const HKEY key, std::vector &tsLocations, std::vector &tsDeleteLocations) { - if (rootkey != "") { - const std::string configLocation = ReadRegValue64(key, "SOFTWARE\\TeamSpeak 3 Client", "ConfigLocation"); - if (configLocation == "0") { - tsLocations.push_back(appData); - tsDeleteLocations.push_back(rootkey); - } else { - tsLocations.push_back(rootkey); - tsDeleteLocations.push_back(rootkey + "\\config"); - } - } -} - void __stdcall RVExtension(char *output, int outputSize, const char *function) { size_t id_length = 1; std::string functionStr(function); @@ -287,249 +59,188 @@ void __stdcall RVExtension(char *output, int outputSize, const char *function) { params = functionStr.substr(id_length, functionStr.length() - id_length); } - const SteamCommand command = static_cast(std::atoi(id.c_str())); + const auto command = static_cast(std::atoi(id.c_str())); - if (skip_plugin_copy()) { + std::string cmd_line = std::string(GetCommandLineA()); + idi::acre::Arguments cmd_args(cmd_line); + if (cmd_args.has_argument("-skipAcrePluginCopy")) { return; } - switch(command) { - case SteamCommand::Check: { + const bool skip_ts_plugin = cmd_args.has_argument("-skipAcreTSPluginCopy"); + const bool skip_mumble_plugin = cmd_args.has_argument("-skipAcreMumblePluginCopy"); + + const std::string mumble_path = cmd_args.get_argument("-mumblePath"); + + idi::acre::TS3Plugin ts3_plugin(skip_ts_plugin); + idi::acre::MumblePlugin mumble_plugin(skip_mumble_plugin, mumble_path); + + switch (command) { + case SteamCommand::check: { strncpy(output, "1", outputSize); return; } - case SteamCommand::GetPath: { - std::string path = find_mod_file("ACRE2Steam.dll"); - path = ReadRegValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\TeamSpeak 3 Client", ""); + case SteamCommand::get_path: { + std::string path; + if (skip_ts_plugin) { + path = ts3_plugin.read_reg_value(HKEY_LOCAL_MACHINE, ts3_plugin.get_registry_key().c_str(), "", false); + } + + if (skip_mumble_plugin) { + path.append("\n" + mumble_plugin.read_reg_value(HKEY_LOCAL_MACHINE, mumble_plugin.get_registry_key().c_str(), "", false)); + } strncpy(output, path.c_str(), outputSize); return; } - case SteamCommand::DoCopy: { + case SteamCommand::do_copy: { std::string current_version = params; - const std::string path_x86 = find_mod_file("plugin\\acre2_win32.dll"); - const std::string path_x64 = find_mod_file("plugin\\acre2_win64.dll"); - if ((path_x86 == "") || (path_x64 == "")) { - const std::string message_string = "ACRE2 was unable to find TeamSpeak 3 plugin file.\n\nMissing file: " + find_mod_folder() + "plugin\\acre2_win32.dll\n\nThe ACRE2 installation is likely corrupted. Please reinstall."; - const int32_t result = MessageBoxA(nullptr, (LPCSTR) message_string.c_str(), "ACRE2 Installation Error", MB_OK | MB_ICONERROR); - //strncpy(output, "[-1]", outputSize); + // Check ACRE2 installation + if (!ts3_plugin.check_acre_installation() || !mumble_plugin.check_acre_installation()) { + std::vector missing_plugins = ts3_plugin.get_missing_acre_plugins(); + missing_plugins.insert(missing_plugins.end(), std::begin(mumble_plugin.get_missing_acre_plugins()), + std::end(mumble_plugin.get_missing_acre_plugins())); + + std::ostringstream oss; + oss << "ACRE2 was unable to find ACRE2 plugin files.\n\n"; + for (const auto &missing : missing_plugins) { + oss << "\t" << missing << "\n"; + } + oss << "\n\nThe ACRE2 installation is likely corrupted. Please reinstall."; + + const std::int32_t result = + MessageBoxA(nullptr, (LPCSTR) oss.str().c_str(), "ACRE2 Installation Error", MB_OK | MB_ICONERROR); TerminateProcess(GetCurrentProcess(), 0); return; } - std::vector ts_locations; // Locations to copy the TS dll to. - std::vector ts_delete_locations; // Locations to remove the TS dll from. - - // Teamspeak 3.1 location - Default location - Roaming Appdata. - wchar_t *appDataRoaming = nullptr; - SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &appDataRoaming); - - std::wstringstream ssAppData; - ssAppData << appDataRoaming; - const std::wstring ws = ssAppData.str(); - std::string appData(ws.begin(), ws.end()); - appData += "\\TS3Client"; - CoTaskMemFree(appDataRoaming); //Free it up. - - /* 32 Bit - Machine */ - std::string rootkey = ReadRegValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\TeamSpeak 3 Client", ""); - checkTsLocations(appData, rootkey, HKEY_LOCAL_MACHINE, ts_locations, ts_delete_locations); - - /* 64 Bit - Machine */ - rootkey = ReadRegValue64(HKEY_LOCAL_MACHINE, "SOFTWARE\\TeamSpeak 3 Client", ""); - checkTsLocations(appData, rootkey, HKEY_LOCAL_MACHINE, ts_locations, ts_delete_locations); - - /* 32 Bit - User */ - rootkey = ReadRegValue(HKEY_CURRENT_USER, "Software\\TeamSpeak 3 Client", ""); - checkTsLocations(appData, rootkey, HKEY_CURRENT_USER, ts_locations, ts_delete_locations); - - /* 64 Bit - User */ - rootkey = ReadRegValue64(HKEY_CURRENT_USER, "Software\\TeamSpeak 3 Client", ""); - checkTsLocations(appData, rootkey, HKEY_CURRENT_USER, ts_locations, ts_delete_locations); - - // Remove duplicates - std::vector unique_ts_locations; - std::vector unique_delete_ts_locations; - for (auto location : ts_delete_locations) { - if (location != "") { - bool found = false; - for (auto unique_ts : unique_delete_ts_locations) { - if (location == unique_ts) { - found = true; - break; - } - } - if (!found) { - unique_delete_ts_locations.push_back(location); - } + const bool ts3_locations_success = ts3_plugin.collect_plugin_locations(); + const bool mumble_locations_success = mumble_plugin.collect_plugin_locations(); + + if (!ts3_locations_success && !mumble_locations_success) { + const std::int32_t result = MessageBoxA(nullptr, + "ACRE2 was unable to find a TeamSpeak 3 or a Mumble installation. If you do have an installation please copy the plugins " + "yourself or reinstall TeamSpeak 3 or Mumble.\n\nIf you are sure you have TeamSpeak 3 and/or Mumble installed and wish to " + "prevent this message from appearing again remove ACRE2Steam.dll and ACRE2Steam_x64.dll from your @acre2 " + "folder.\n\nContinue anyway?", + "ACRE2 Installation Error", MB_YESNO | MB_ICONEXCLAMATION); + if (result == IDYES) { + strncpy(output, "[-3,true]", outputSize); + return; } + + TerminateProcess(GetCurrentProcess(), 0); + return; } - for (auto location : ts_locations) { - if (location != "") { - bool found = false; - for (auto unique_ts : unique_ts_locations) { - if (location == unique_ts) { - found = true; - break; - } + bool try_copy = false; + + do { + std::future update_ts3 = + std::async(std::launch::async, [&]() { return ts3_plugin.handle_update_plugin(); }); + std::future update_mumble = + std::async(std::launch::async, [&]() { return mumble_plugin.handle_update_plugin(); }); + + const idi::acre::UpdateCode ts3_update_result = update_ts3.get(); + const idi::acre::UpdateCode mumble_update_result = update_mumble.get(); + + std::string error_msg; + const bool update_ts3_ok = + (ts3_update_result != idi::acre::UpdateCode::update_failed) && (ts3_update_result != idi::acre::UpdateCode::other); + if (!update_ts3_ok) { + error_msg = ts3_plugin.get_last_error_message(); + } + + const bool update_mumble_ok = + (mumble_update_result != idi::acre::UpdateCode::update_failed) && (mumble_update_result != idi::acre::UpdateCode::other); + if (!update_mumble_ok) { + error_msg = mumble_plugin.get_last_error_message(); + } + + if (!update_ts3_ok || !update_mumble_ok) { + std::ostringstream oss; + oss << "ACRE2 was unable to copy the Mumble/TeamSpeak 3 plugin. Please check if you have write access to the plugin " + << "folder, close any instances of TeamSpeak 3 and/or Mumble and click \"Try Again\".\n\nIf you " + << "would like to close Arma 3 click Cancel. Press Continue to launch Arma 3 regardless.\n\n" + << error_msg; + + + const int32_t result = MessageBoxA( + nullptr, (LPCSTR) oss.str().c_str(), "ACRE2 Installation Error", MB_CANCELTRYCONTINUE | MB_ICONEXCLAMATION); + if (result == IDCANCEL) { + TerminateProcess(GetCurrentProcess(), 0); + return; + } else if (result == IDCONTINUE) { + sprintf(output, "[-4,true,%d %d]", ts3_plugin.get_last_error(), mumble_plugin.get_last_error()); + return; } - if (!found) { - unique_ts_locations.push_back(location); + } else { + try_copy = true; - // If we are going to copy to a folder do not remove from the plugin from it. - unique_delete_ts_locations.erase(std::remove(unique_delete_ts_locations.begin(), unique_delete_ts_locations.end(), location), unique_delete_ts_locations.end()); + // Update was not necessary. + if ((ts3_update_result == idi::acre::UpdateCode::update_not_necessary) && + (mumble_update_result == idi::acre::UpdateCode::update_not_necessary)) { // No update was copied etc. + strncpy(output, "[0]", outputSize); + return; } } - } + } while (!try_copy); - // No locations to copy to. - if (unique_ts_locations.size() == 0) { - const int32_t result = MessageBoxA(nullptr, "ACRE2 was unable to find a TeamSpeak 3 installation. If you do have an installation please copy the plugins yourself or reinstall TeamSpeak 3. \n\n If you are sure you have TeamSpeak 3 installed and wish to prevent this message from appearing again remove ACRE2Steam.dll and ACRE2Steam_x64.dll from your @acre2 folder.\n\nContinue anyway?", "ACRE2 Installation Error", MB_YESNO | MB_ICONEXCLAMATION); - if (result == IDYES) { - strncpy(output, "[-3,true]", outputSize); - return; - } else { - TerminateProcess(GetCurrentProcess(), 0); - return; + std::ostringstream oss; + oss << "A new version of ACRE2 (" << current_version << ") has been installed!\n\n"; + + std::string found_paths; + if (!ts3_plugin.get_updated_paths().empty()) { + oss << "The TeamSpeak 3 plugins have been copied to the following location(s):\n"; + for (const auto &path : ts3_plugin.get_updated_paths()) { + oss << path << "\n"; + found_paths.append(path + "\n"); } + + oss << "\n"; } - bool updateRequired = false; - std::string found_paths = ""; - for (auto location : unique_ts_locations) { - bool try_copy = true; - - // Skip directory if the folder the plugins folder should be in does not exist. - DWORD location_folder_attr = GetFileAttributes(location.c_str()); - if (location_folder_attr == INVALID_FILE_ATTRIBUTES) { continue; } - if (!(location_folder_attr & FILE_ATTRIBUTE_DIRECTORY)) { continue; } - - const std::string plugin_folder = location + "\\plugins"; - - // If plugins folder does not exist - create it - CreateDirectory(plugin_folder.c_str(), nullptr); // This will silently fail if it exists - Exactly what we want. - - const std::string ts_path_x86 = plugin_folder + "\\acre2_win32.dll"; - const std::string ts_path_x64 = plugin_folder + "\\acre2_win64.dll"; - if (!compare_file(ts_path_x86, path_x86) || !compare_file(ts_path_x64, path_x64)) { // Only copy if files don't match. - updateRequired = true; - do { - if (!CopyFileA((LPCSTR)path_x86.c_str(), (LPCSTR)ts_path_x86.c_str(), false) || !CopyFileA((LPCSTR)path_x64.c_str(), (LPCSTR)ts_path_x64.c_str(), false)) { - DWORD last_error = GetLastError(); - if (last_error == 32) { - const int32_t result = MessageBoxA(nullptr, "ACRE2 was unable to copy the TeamSpeak 3 plugin due to it being in use. Please close any instances of TeamSpeak 3 and click \"Try Again\".\n\nIf you would like to close Arma 3, click Cancel. Press Continue to launch Arma 3 regardless.", "ACRE2 Installation Error", MB_CANCELTRYCONTINUE | MB_ICONEXCLAMATION); - if (result == IDCANCEL) { - TerminateProcess(GetCurrentProcess(), 0); - return; - } else if (result == IDCONTINUE) { - sprintf(output, "[-4,true,%d]", last_error); - return; - } - } else { // Not error 32 - const int32_t result = MessageBoxA(nullptr, "ACRE2 was unable to copy the TeamSpeak 3 plugin. This is most likely because Steam is not running with admin privileges. Please restart Steam as administrator (right click on the shortcut -> run as administrator) and relaunch Arma 3 with ACRE2 loaded. You only need to do this when ACRE2 is updated. \n\nWould you like to run Arma 3 anyway?", "ACRE2 Installation Error", MB_YESNO | MB_ICONEXCLAMATION); - if (result == IDNO) { - TerminateProcess(GetCurrentProcess(), 0); - return; - } else { // Result is yes continue - sprintf(output, "[-5,true,%d]", last_error); - return; - } - } - } else { - found_paths += location + "\n"; - try_copy = false; - } - } while (try_copy); + if (!mumble_plugin.get_updated_paths().empty()) { + oss << "The Mumble plugins have been copied to the following location(s):\n"; + + std::string arch_installed = ""; + if (mumble_plugin.get_arch_to_install() == idi::acre::Architecture::x32) { + arch_installed = " [32-bit only]"; + } else if (mumble_plugin.get_arch_to_install() == idi::acre::Architecture::x64) { + arch_installed = " [64-bit only]"; } - } - std::string remove_paths = ""; - for (auto location : unique_delete_ts_locations) { - const std::string plugin_folder = location + "\\plugins"; - - // Skip directory if the plugins folder does not exist. - DWORD plugin_folder_attr = GetFileAttributes(plugin_folder.c_str()); - if (plugin_folder_attr == INVALID_FILE_ATTRIBUTES) { continue; } - if (!(plugin_folder_attr & FILE_ATTRIBUTE_DIRECTORY)) { continue; } - - std::vector files; - const std::string ts_path_x64 = plugin_folder + "\\acre2_win64.dll"; - files.push_back(ts_path_x64); - const std::string ts_path_x86 = plugin_folder + "\\acre2_win32.dll"; - files.push_back(ts_path_x86); - - bool popupLocationNotify = false; - for (auto file : files) { - bool try_delete = true; - { - if (!DeleteFileA(file.c_str())) { - DWORD last_error = GetLastError(); - if (last_error != ERROR_FILE_NOT_FOUND) { - updateRequired = true; - if (last_error == FILE_SHARE_DELETE) { // File in use - const int32_t result = MessageBoxA(nullptr, "ACRE2 is unable to copy the TeamSpeak 3 plugin due to it being in use. Please close any instances of TeamSpeak 3 and click \"Try Again\".\n\nIf you would like to close Arma 3, click Cancel. Press Continue to launch Arma 3 regardless.", "ACRE2 Installation Error", MB_CANCELTRYCONTINUE | MB_ICONEXCLAMATION); - if (result == IDCANCEL) { - TerminateProcess(GetCurrentProcess(), 0); - return; - } else if (result == IDCONTINUE) { - sprintf(output, "[-4,true,%d]", last_error); - return; - } - } else { - // ERROR_ACCESS_DENIED. - const std::string message = "ACRE2 was unable to remove an old version of the TeamSpeak 3 plugin. You have two options:\n\n1) Manually delete the following file: \n" + file + "\n\n2) Restart Steam and Arma 3 as administrator (right click on the shortcut -> run as administrator) and relaunch Arma 3 with ACRE2 loaded. \n\nWould you like to run Arma 3 anyway?"; - const int32_t result = MessageBoxA(nullptr, (LPCSTR)message.c_str(), "ACRE2 Installation Error", MB_YESNO | MB_ICONEXCLAMATION); - if (result == IDNO) { - TerminateProcess(GetCurrentProcess(), 0); - return; - } else { //result is yes continue - sprintf(output, "[-5,true,%d]", last_error); - return; - } - } - } else { - // The dll was already removed, exit the loop. - try_delete = false; - } - } else { - // Successfully deleted the dll. - updateRequired = true; - try_delete = false; - if (!popupLocationNotify) { - // Prevent the location from being outputted to the pop-up twice. - remove_paths += location + "\n"; - popupLocationNotify = true; - } - } - } while (try_delete); + for (const auto &path : mumble_plugin.get_updated_paths()) { + oss << path << arch_installed << "\n"; + found_paths.append(path + "\n"); } + + oss << "\n"; } + if (!ts3_plugin.get_removed_paths().empty()) { + oss << "The TeamSpeak 3 plugin has been removed from the following location(s):\n"; + for (const auto &path : ts3_plugin.get_removed_paths()) { + oss << path << "\n"; + } - if (!updateRequired) { // No update was copied etc. - strncpy(output, "[0]", outputSize); - return; + oss << "\n"; } - std::stringstream ss; - ss << "A new version of ACRE2 (" << current_version << ") has been installed!\n"; - ss << "\n"; - if (found_paths != "") { - ss << "The TeamSpeak 3 plugins have been copied to the following location(s):\n" << found_paths; - ss << "\n"; - } - if (remove_paths != "") { - ss << "The TeamSpeak 3 plugin has been removed from the following location(s):\n" << remove_paths << "\n"; - if (found_paths == "") { - ss << "An update to version is already in:\n" << unique_ts_locations.at(0) << "\n"; - ss << "\n"; + if (!mumble_plugin.get_removed_paths().empty()) { + oss << "The Mumble plugin has been removed from the following location(s):\n"; + for (const auto &path : mumble_plugin.get_removed_paths()) { + oss << path << "\n"; } + + oss << "\n"; } - ss << "If this is NOT valid, please uninstall all versions of TeamSpeak 3 and reinstall both it and ACRE2 or copy the plugins manually to your correct installation.\n"; - ss << "\n"; - ss << "If this appears to be the correct folder(s) please remember to enable the plugin in TeamSpeak 3!"; - const int32_t result = MessageBoxA(nullptr, (LPCSTR)ss.str().c_str(), "ACRE2 Installation Success", MB_OK | MB_ICONINFORMATION); + + oss << "If this is NOT valid, please uninstall all versions of Mumble and/or TeamSpeak 3 and reinstall both it and ACRE2 or " + << "copy the plugins manually to your correct installation.\n\n" + << "If this appears to be the correct folder(s) please remember to enable the plugin in Mumble and/or TeamSpeak 3!"; + const int32_t result = + MessageBoxA(nullptr, (LPCSTR) oss.str().c_str(), "ACRE2 Installation Success", MB_OK | MB_ICONINFORMATION); sprintf(output, "[1,\"%s\"]", found_paths.c_str()); return; @@ -539,33 +250,20 @@ void __stdcall RVExtension(char *output, int outputSize, const char *function) { } } +void Init() {} +void Cleanup() {} - -void Init(void) { - -} - -void Cleanup(void) { - -} - - -BOOL APIENTRY DllMain( HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) -{ - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - Init(); - break; - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - case DLL_PROCESS_DETACH: - Cleanup(); - break; +BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) { + switch (ul_reason_for_call) { + case DLL_PROCESS_ATTACH: + Init(); + break; + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + case DLL_PROCESS_DETACH: + Cleanup(); + break; } return TRUE; } diff --git a/extensions/src/ACRE2Steam/command_options.cpp b/extensions/src/ACRE2Steam/command_options.cpp new file mode 100644 index 000000000..ef0fce4e9 --- /dev/null +++ b/extensions/src/ACRE2Steam/command_options.cpp @@ -0,0 +1,74 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * @author Thymo- + * + * @copyright Copyright (c) 2020 International Development & Integration Systems LLC + * + * Command line options for ACRE2. + */ + +#include "command_options.hpp" + +#include +#include + +using ::idi::acre::Arguments; + +std::string Arguments::get_argument(const std::string &key_) const noexcept { + const auto it = arguments.find(key_); + + if (it == arguments.cend()) { + return ""; + } + + return it->second; +} + +bool Arguments::has_argument(const std::string &key_) const noexcept { + return arguments.cend() != arguments.find(key_); +} + +void Arguments::parse_line(const std::string &line_) noexcept(false) { + std::istringstream iss(line_); + std::string arg; + while (std::getline(iss, arg, ' ')) { + const std::string par_opt("-par="); + std::string par_path; + if (arg.find("-par") != std::string::npos) { + par_path = arg.substr(par_opt.length()); + + std::ifstream par_file(par_path); + + // Append all options from the parameter file + if (!par_file.is_open()) { + throw std::runtime_error("Parameters file could not be opened!"); + } + + std::string par_line; + std::getline(par_file, par_line); + if (par_line == "class Arg") { + par_file.close(); + throw std::runtime_error("Old file format detected"); + } + + par_file.seekg(0, std::ifstream::beg); + + while (std::getline(par_file, par_line)) { + handle_argument(par_line); + } + + par_file.close(); + } else { + handle_argument(arg); + } + } +} + +void Arguments::handle_argument(const std::string &arg_) noexcept { + const auto &option_pos = arg_.find('='); + if (option_pos != std::string::npos) { + arguments[arg_.substr(0, option_pos)] = arg_.substr(option_pos + 1, arg_.length() - option_pos); + } else { + arguments[arg_] = ""; + } +} diff --git a/extensions/src/ACRE2Steam/command_options.hpp b/extensions/src/ACRE2Steam/command_options.hpp new file mode 100644 index 000000000..8a7b04f05 --- /dev/null +++ b/extensions/src/ACRE2Steam/command_options.hpp @@ -0,0 +1,30 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * + * @copyright Copyright (c) 2020 International Development & Integration Systems LLC + * + * Mumble auto-plugin copy functionality. + */ +#pragma once + +#include +#include + +namespace idi::acre { + class Arguments { + public: + explicit Arguments(std::string &line_) noexcept(false) { parse_line(line_); } + + ~Arguments() = default; + + std::string get_argument(const std::string &key_) const noexcept; + + bool has_argument(const std::string &key_) const noexcept; + + private: + void parse_line(const std::string &line_) noexcept(false); + void handle_argument(const std::string &arg_) noexcept; + + std::unordered_map arguments; + }; +} // namespace idi::acre diff --git a/extensions/src/ACRE2Steam/mumble_plugin.cpp b/extensions/src/ACRE2Steam/mumble_plugin.cpp new file mode 100644 index 000000000..a3f611224 --- /dev/null +++ b/extensions/src/ACRE2Steam/mumble_plugin.cpp @@ -0,0 +1,87 @@ +#include "mumble_plugin.hpp" + +#include + +using ::idi::acre::MumblePlugin; + +bool MumblePlugin::collect_plugin_locations() noexcept { + if (get_skip_plugin()) { + return true; + } + + if (!mumble_path.empty()) { + // Install both plugins if a path is given, as there is no official portable Mumble + // and a warning in chat window is fine for development purposes + check_plugin_locations(mumble_path); + } else { + // Mumble location - Default location - Roaming Appdata. + wchar_t *app_data_roaming = nullptr; + SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &app_data_roaming); + + if (app_data_roaming == nullptr) { + return false; + } + + // Convert to UTF-8 string + std::string app_data = VOIPPlugin::wide_string_to_utf8(app_data_roaming); + CoTaskMemFree(app_data_roaming); // Free it up. + + // Path to install into (AppData/Roaming) + app_data.append("\\Mumble"); + + // Pick which architecture of the plugin to install to avoid Mumble showing warnings in the chat window + bool x32_installed = false; + bool x64_installed = false; + std::array folder_ids = {FOLDERID_ProgramFilesX86, FOLDERID_ProgramFilesX64}; + for (const auto &folder : folder_ids) { + std::string program_data; + + wchar_t *folder_path = nullptr; + SHGetKnownFolderPath(folder, 0, nullptr, &folder_path); + if (folder_path == nullptr) { +#ifdef _WIN64 + continue; +#else + // FOLDERID_ProgramFilesX64 (or use of it in SHGetKnownFolderPath) is not supported in a 32-bit application, + // but we may want to install x64 Mumble plugin while running x32 Arma, + // fall back to registry read of x64 Program Files location + program_data = read_reg_value(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", "ProgramFilesDir", true); + if (program_data.empty()) { + continue; + } +#endif + } else { + // Convert to UTF-8 string + program_data = VOIPPlugin::wide_string_to_utf8(folder_path); + } + CoTaskMemFree(folder_path); // Free it up. + + program_data.append("\\Mumble"); + + if (std::filesystem::exists(program_data)) { + if (folder == folder_ids[0]) { + x32_installed = true; + } else if (folder == folder_ids[1]) { + x64_installed = true; + } + } + } + + // Mumble is not installed at all, don't copy plugins + if (!x32_installed && !x64_installed) { + return false; + } + + // Otherwise copy to AppData and set the found architecture + check_plugin_locations(app_data); + + if (x32_installed && !x64_installed) { + set_arch_to_install(Architecture::x32); + } else if (!x32_installed && x64_installed) { + set_arch_to_install(Architecture::x64); + } + } + + // No locations to copy to. + return !get_plugin_locations().empty(); +} diff --git a/extensions/src/ACRE2Steam/mumble_plugin.hpp b/extensions/src/ACRE2Steam/mumble_plugin.hpp new file mode 100644 index 000000000..7c018c16b --- /dev/null +++ b/extensions/src/ACRE2Steam/mumble_plugin.hpp @@ -0,0 +1,30 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * + * @copyright Copyright (c) 2020 International Development & Integration Systems LLC + * + * Mumble auto-plugin copy functionality. + */ +#pragma once +#include "voip_plugin.hpp" + +#include +#include + +namespace idi::acre { + class MumblePlugin final : public VOIPPlugin { + public: + explicit MumblePlugin(bool skip_plugin_, std::string mumble_path_ = "") noexcept + : VOIPPlugin(skip_plugin_, + "SOFTWARE\\Mumble\\Mumble\\plugins", + find_mod_file("plugin/mumble/acre2_win32.dll"), + find_mod_file("plugin/mumble/acre2_win64.dll")), + mumble_path(std::move(mumble_path_)) {} + ~MumblePlugin() noexcept final = default; + + bool collect_plugin_locations() noexcept final; + + private: + std::string mumble_path; + }; +} // namespace idi::acre diff --git a/extensions/src/ACRE2Steam/ts3_plugin.cpp b/extensions/src/ACRE2Steam/ts3_plugin.cpp new file mode 100644 index 000000000..289ce7b9a --- /dev/null +++ b/extensions/src/ACRE2Steam/ts3_plugin.cpp @@ -0,0 +1,59 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * @author Cliff Foster (Nou) + * @author James Smith (Snippers) + * + * @copyright Copyright (c) 2020 International Development & Integration + * Systems LLC + * + * TeamSpeak 3 auto-plugin copy functionality. + */ +#include "ts3_plugin.hpp" + +#include + +using ::idi::acre::TS3Plugin; + +bool TS3Plugin::collect_plugin_locations() noexcept { + if (get_skip_plugin()) { + return true; + } + + // Teamspeak 3 location - Default location - Roaming Appdata. + wchar_t *app_data_roaming = nullptr; + SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &app_data_roaming); + + if (app_data_roaming == nullptr) { + return false; + } + + // Convert to UTF-8 string + std::string app_data = VOIPPlugin::wide_string_to_utf8(app_data_roaming); + + app_data.append("\\TS3Client"); + CoTaskMemFree(app_data_roaming); // Free it up. + + const std::array registry_keys = {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}; + + for (const auto &key : registry_keys) { + // 32 bits + std::string rootkey = read_reg_value(key, get_registry_key().c_str(), "", false); + check_plugin_locations(app_data, rootkey, key); + + // 64 bits + rootkey = read_reg_value(key, get_registry_key().c_str(), "", true); + check_plugin_locations(app_data, rootkey, key); + } + + // Do not delete if we need to copy it + std::vector ts3_locations = get_plugin_locations(); + std::vector ts3_delete_locations = get_plugin_delete_locations(); + + for (const auto &location : ts3_locations) { + ts3_delete_locations.erase( + std::remove(ts3_delete_locations.begin(), ts3_delete_locations.end(), location), ts3_delete_locations.end()); + } + + // No locations to copy to. + return !get_plugin_locations().empty(); +} diff --git a/extensions/src/ACRE2Steam/ts3_plugin.hpp b/extensions/src/ACRE2Steam/ts3_plugin.hpp new file mode 100644 index 000000000..a6d385937 --- /dev/null +++ b/extensions/src/ACRE2Steam/ts3_plugin.hpp @@ -0,0 +1,27 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * @author Cliff Foster (Nou) + * @author James Smith (Snippers) + * + * @copyright Copyright (c) 2020 International Development & Integration Systems LLC + * + * TeamSpeak 3 auto-plugin copy functionality. + */ +#include "voip_plugin.hpp" + +#include +#include + +namespace idi::acre { + class TS3Plugin final : public VOIPPlugin { + public: + explicit TS3Plugin(bool skip_plugin_) noexcept + : VOIPPlugin(skip_plugin_, + "SOFTWARE\\TeamSpeak 3 Client", + find_mod_file("plugin/ts3/acre2_win32.dll"), + find_mod_file("plugin/ts3/acre2_win64.dll")) {} + ~TS3Plugin() noexcept final = default; + + bool collect_plugin_locations() noexcept final; + }; +} // namespace idi::acre diff --git a/extensions/src/ACRE2Steam/voip_plugin.cpp b/extensions/src/ACRE2Steam/voip_plugin.cpp new file mode 100644 index 000000000..7a279d560 --- /dev/null +++ b/extensions/src/ACRE2Steam/voip_plugin.cpp @@ -0,0 +1,249 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * @author Cliff Foster (Nou) + * @author James Smith (Snippers) + * + * @copyright Copyright (c) 2020 International Development & Integration Systems LLC + * + * Voice over IP auto-plugin copy functionality. + */ + +#include "voip_plugin.hpp" + +#include +#include +#include +#include + +using ::idi::acre::VOIPPlugin; + +std::string idi::acre::find_mod_folder() { + char module_path[MAX_PATH]; + GetModuleFileNameA((HINSTANCE) &__ImageBase, module_path, MAX_PATH); + + char drive[_MAX_DRIVE]; + char dir[_MAX_DIR]; + + _splitpath(module_path, drive, dir, nullptr, nullptr); + + return (std::string(drive) + std::string(dir)); +} + +std::string idi::acre::find_mod_file(const std::string &filename) { + std::string path = find_mod_folder() + filename; + if (!std::filesystem::exists(path)) { + // No mod path was set, it means they used the mod config. It *DOES* mean it relative to a folder in our path at least. + // So, we just search all the local folders + + WIN32_FIND_DATAA data; + std::string path(""); + HANDLE hFile = FindFirstFileA(path.c_str(), &data); + + if (hFile == INVALID_HANDLE_VALUE) { + return ""; + } + + while ((FindNextFile(hFile, &data) != 0) || (GetLastError() != ERROR_NO_MORE_FILES)) { + if (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + const std::string fullpath = std::string(data.cFileName) + filename; + if (std::filesystem::exists(fullpath)) { + path = fullpath; + break; + } + } + } + } + return path; +} + +std::string VOIPPlugin::read_reg_value(HKEY root_, const std::string &key_, const std::string &name_, const bool use_x64_) noexcept { + REGSAM sam_key = KEY_READ | KEY_WOW64_64KEY; + + if (!use_x64_) { + sam_key = KEY_READ; + } + + HKEY hkey; + if (RegOpenKeyExA(root_, key_.c_str(), 0, sam_key, &hkey) != ERROR_SUCCESS) { + return ""; + } + + DWORD type; + DWORD cbData; + if (RegQueryValueExA(hkey, name_.c_str(), nullptr, &type, nullptr, &cbData) != ERROR_SUCCESS) { + RegCloseKey(hkey); + return ""; + } + + if (type != REG_SZ) { + RegCloseKey(hkey); + return ""; + } + + std::string value(cbData / sizeof(char), '\0'); + if (RegQueryValueExA(hkey, name_.c_str(), nullptr, nullptr, reinterpret_cast(&value[0]), &cbData) != ERROR_SUCCESS) { + RegCloseKey(hkey); + return ""; + } + + RegCloseKey(hkey); + + size_t firstNull = value.find_first_of('\0'); + if (firstNull != std::string::npos) { + value.resize(firstNull); + } + + return value; +} + +bool VOIPPlugin::compare_file(const std::string &path_a_, const std::string &path_b_) noexcept { + // Open both files ath the end to check their size + std::ifstream fileA(path_a_, std::ifstream::ate | std::ifstream::binary); + std::ifstream fileB(path_b_, std::ifstream::ate | std::ifstream::binary); + + if (fileA.tellg() != fileB.tellg()) { + return false; + } + + // Files are of the same size. Rewind and compare file contents + fileA.seekg(0); + fileB.seekg(0); + + std::istreambuf_iterator beginA(fileA); + std::istreambuf_iterator beginB(fileB); + + return std::equal(beginA, std::istreambuf_iterator(), beginB); +} + +void VOIPPlugin::check_plugin_locations(const std::string &app_data_) noexcept { + if (std::filesystem::exists(app_data_) && + (plugin_locations.cend() == std::find(plugin_locations.cbegin(), plugin_locations.cend(), app_data_))) { + plugin_locations.emplace_back(app_data_); + } +} + +void VOIPPlugin::check_plugin_locations(const std::string &app_data_, const std::string &root_key_, const HKEY key_) noexcept { + if (root_key_.empty()) { + return; + } + + const std::string config_location = read_reg_value(key_, registry_key, "ConfigLocation", true); + if (config_location == "0") { + if (plugin_locations.end() == std::find(plugin_locations.cbegin(), plugin_locations.cend(), app_data_)) { + plugin_locations.push_back(app_data_); + } + + if (plugin_delete_locations.end() == std::find(plugin_delete_locations.cbegin(), plugin_delete_locations.cend(), root_key_)) { + plugin_delete_locations.push_back(root_key_); + } + } else { + if (plugin_locations.end() == std::find(plugin_locations.cbegin(), plugin_locations.cend(), root_key_)) { + plugin_locations.push_back(root_key_); + } + + if (plugin_delete_locations.end() == + std::find(plugin_delete_locations.cbegin(), plugin_delete_locations.cend(), root_key_ + "/config")) { + plugin_delete_locations.push_back(root_key_ + "/config"); + } + } +} + +bool VOIPPlugin::check_acre_installation() noexcept { + const bool x32_plugin_exist = std::filesystem::exists(x32_acre_plugin); + const bool x64_plugin_exist = std::filesystem::exists(x64_acre_plugin); + const bool x32_install = arch_to_install == Architecture::both || arch_to_install == Architecture::x32; + const bool x64_install = arch_to_install == Architecture::both || arch_to_install == Architecture::x64; + + if (!x32_plugin_exist && x32_install) { + missing_acre_plugins.emplace_back(x32_acre_plugin.filename().string()); + } + + if (!x64_plugin_exist && x64_install) { + missing_acre_plugins.emplace_back(x64_acre_plugin.filename().string()); + } + + return (x32_plugin_exist || !x32_install) && (x64_plugin_exist || !x64_install); +} + +idi::acre::UpdateCode VOIPPlugin::handle_update_plugin() noexcept { + if (skip_plugin) { + return UpdateCode::update_not_necessary; + } + + UpdateCode update_status = UpdateCode::update_not_necessary; + + // Clean the error messages in case of retrying. + if (!last_error_msg.empty()) { + last_error_msg.clear(); + } + + if (!updated_paths.empty()) { + updated_paths.clear(); + } + + for (const auto &location : plugin_locations) { + std::filesystem::path plugin_folder(location + "/plugins"); + + std::error_code err_code; + if (!std::filesystem::exists(plugin_folder) && !std::filesystem::create_directory(plugin_folder, err_code)) { + last_error_msg.append(err_code.message()); + last_error = err_code; + + return UpdateCode::other; + } + + std::vector> plugin_paths_array; + if (arch_to_install == Architecture::both || arch_to_install == Architecture::x32) { + plugin_paths_array.push_back(std::make_pair(plugin_folder / "acre2_win32.dll", x32_acre_plugin)); + } + if (arch_to_install == Architecture::both || arch_to_install == Architecture::x64) { + plugin_paths_array.push_back(std::make_pair(plugin_folder / "acre2_win64.dll", x64_acre_plugin)); + } + + for (const auto &path : plugin_paths_array) { + if (!compare_file(path.first.string(), path.second.string())) { + bool copy_ok = + std::filesystem::copy_file(path.second, path.first, std::filesystem::copy_options::overwrite_existing, err_code); + if (!copy_ok) { + last_error_msg.append(err_code.message()); + last_error = err_code; + + return UpdateCode::update_failed; + } + + if (updated_paths.cend() == std::find(updated_paths.cbegin(), updated_paths.cend(), location)) { + updated_paths.emplace_back(location); + } + update_status = UpdateCode::update_ok; + } + } + } + + for (const auto &location : plugin_delete_locations) { + std::filesystem::path plugin_folder(location + "plugins"); + + if (!std::filesystem::exists(plugin_folder)) { + continue; + } + + std::array plugin_paths_array = {plugin_folder / "acre2_win32.dll", plugin_folder / "acre2_win64.dll"}; + + for (const auto &path : plugin_paths_array) { + std::error_code err_code; + bool remove_ok = std::filesystem::remove(path, err_code); + if (!remove_ok) { + last_error_msg.append(err_code.message()); + last_error = err_code; + + return UpdateCode::update_failed; + } + + if (removed_paths.cend() == std::find(removed_paths.cbegin(), removed_paths.cend(), location)) { + removed_paths.emplace_back(location); + } + update_status = UpdateCode::update_ok; + } + } + + return update_status; +} diff --git a/extensions/src/ACRE2Steam/voip_plugin.hpp b/extensions/src/ACRE2Steam/voip_plugin.hpp new file mode 100644 index 000000000..17a8b2073 --- /dev/null +++ b/extensions/src/ACRE2Steam/voip_plugin.hpp @@ -0,0 +1,119 @@ +/** + * @author Ferran Obón Santacana (Magnetar) + * @author Cliff Foster (Nou) + * @author James Smith (Snippers) + * + * + * @copyright Copyright (c) 2020 International Development & Integration Systems LLC + * + * Voice over IP auto-plugin copy functionality. + */ +#pragma once + +#include "Shlwapi.h" +#include "shlobj.h" + +#include +#include +#include + +namespace idi::acre { + + EXTERN_C IMAGE_DOS_HEADER __ImageBase; + + std::string find_mod_folder(); + + std::string find_mod_file(const std::string &filename); + + enum class UpdateCode : std::uint8_t { update_not_necessary, update_ok, update_failed, other }; + enum class Architecture : std::uint8_t { both, x32, x64 }; + + class VOIPPlugin { + public: + explicit VOIPPlugin(bool skip_plugin_, + std::string registry_key_, + const std::string &x32_plugin_path_, + const std::string &x64_plugin_path_) noexcept + : skip_plugin(skip_plugin_), registry_key(std::move(registry_key_)), x32_acre_plugin(x32_plugin_path_), + x64_acre_plugin(x64_plugin_path_) {} + + virtual ~VOIPPlugin() noexcept = default; + + bool check_acre_installation() noexcept; + + std::string get_registry_key() const noexcept { return registry_key; } + + const std::string &get_last_error_message() const noexcept { return last_error_msg; } + std::int32_t get_last_error() const noexcept { return last_error.value(); } + + const std::vector &get_missing_acre_plugins() const noexcept { return missing_acre_plugins; } + + virtual bool collect_plugin_locations() noexcept = 0; + + UpdateCode handle_update_plugin() noexcept; + + const std::vector &get_updated_paths() const noexcept { return updated_paths; } + const std::vector &get_removed_paths() const noexcept { return removed_paths; } + + std::string read_reg_value(HKEY root_, const std::string &key_, const std::string &name_, bool use_x64_) noexcept; + + const Architecture &get_arch_to_install() const noexcept { return arch_to_install; } + + protected: + // Convert a wide Unicode string to an UTF8 string + + /** + * Convert a wide Unicode string to an UTF8 string . + * + * @param[in] wide_str_ Wide unicode string + * + * @return std::string UTF-8 encoded string + */ + static std::string wide_string_to_utf8(const std::wstring &wide_str_) { + if (wide_str_.empty()) { + return ""; + } + + const int32_t size_needed = + WideCharToMultiByte(CP_UTF8, 0, &wide_str_[0], static_cast(wide_str_.size()), nullptr, 0, nullptr, nullptr); + + std::string utf8_str(size_needed, 0); + WideCharToMultiByte( + CP_UTF8, 0, &wide_str_[0], static_cast(wide_str_.size()), &utf8_str[0], size_needed, nullptr, nullptr); + + return utf8_str; + } + + bool get_skip_plugin() const noexcept { return skip_plugin; } + + bool compare_file(const std::string &path_a_, const std::string &path_b_) noexcept; + + void check_plugin_locations(const std::string &app_data_) noexcept; + void check_plugin_locations(const std::string &appData, const std::string &rootkey, const HKEY key) noexcept; + + std::vector &get_plugin_locations() noexcept { return plugin_locations; } + void set_plugin_locations(const std::vector locations_) { plugin_locations = locations_; } + + std::vector &get_plugin_delete_locations() noexcept { return plugin_delete_locations; } + void set_plugin_delete_locations(const std::vector locations_) { plugin_delete_locations = locations_; } + + void set_arch_to_install(Architecture arch_) { arch_to_install = arch_; } + + private: + bool skip_plugin = false; + std::string registry_key; + std::string last_error_msg; + std::error_code last_error; + + std::vector plugin_locations; // Locations to copy the VOIP dll to. + std::vector plugin_delete_locations; // Locations to remove the VOIP dll from. + + std::vector updated_paths; + std::vector removed_paths; + + std::filesystem::path x32_acre_plugin; + std::filesystem::path x64_acre_plugin; + Architecture arch_to_install = Architecture::both; + std::vector missing_acre_plugins; + }; +} // namespace idi::acre diff --git a/extensions/src/ACRE2TS/CMakeLists.txt b/extensions/src/ACRE2TS/CMakeLists.txt index 3623f1b99..bfbb0e4c0 100644 --- a/extensions/src/ACRE2TS/CMakeLists.txt +++ b/extensions/src/ACRE2TS/CMakeLists.txt @@ -25,7 +25,7 @@ endif() add_custom_command(TARGET ${ACRE_NAME} POST_BUILD # Copy DLL to plugins - COMMAND ${CMAKE_COMMAND} -E copy $ ${PROJECT_SOURCE_DIR}/../plugin/${FINAL_DLL_NAME} + COMMAND ${CMAKE_COMMAND} -E copy $ ${PROJECT_SOURCE_DIR}/../plugin/ts3/${FINAL_DLL_NAME} # Copy PDB to symbols COMMAND ${CMAKE_COMMAND} -E copy $/${ACRE_NAME}.pdb ${PROJECT_SOURCE_DIR}/../symbols/${ACRE_ARCH}/${ACRE_NAME}.pdb ) diff --git a/extensions/src/ACRE2TS/CommandServer.cpp b/extensions/src/ACRE2TS/CommandServer.cpp index e24bf7ce5..d71632992 100644 --- a/extensions/src/ACRE2TS/CommandServer.cpp +++ b/extensions/src/ACRE2TS/CommandServer.cpp @@ -9,13 +9,13 @@ extern TS3Functions ts3Functions; -acre::Result CCommandServer::initialize(void){ +acre::Result CCommandServer::initialize(){ TRACE("enter"); return acre::Result::ok; } -acre::Result CCommandServer::shutdown(void) { +acre::Result CCommandServer::shutdown() { TRACE("enter"); return acre::Result::ok; @@ -26,8 +26,8 @@ acre::Result CCommandServer::sendMessage(IMessage *msg){ LOCK(this); //LOG("Sending: %s", (const char *)msg->getData()); ts3Functions.sendPluginCommand((unsigned __int64)ts3Functions.getCurrentServerConnectionHandlerID(), - (const char *)this->getCommandId(), - (const char *)msg->getData(), + (const char *)this->getCommandId(), + (const char *)msg->getData(), PluginCommandTarget_CURRENT_CHANNEL, NULL, NULL); //LOG("sending [%s], [%s]", this->getCommandId(), msg->getData()); @@ -49,10 +49,10 @@ acre::Result CCommandServer::handleMessage(unsigned char *data){ } -acre::Result CCommandServer::release(void) { - - if (this->getCommandId()) +acre::Result CCommandServer::release() { + if (this->getCommandId()) { free(this->getCommandId()); + } return acre::Result::ok; } @@ -60,14 +60,11 @@ acre::Result CCommandServer::release(void) { // // constructor/destructor foo -// +// CCommandServer::CCommandServer(const acre::id_t id) { this->setId(id); } -CCommandServer::CCommandServer(void) { +CCommandServer::CCommandServer() { this->setCommandId(nullptr); this->setConnected(true); } -CCommandServer::~CCommandServer() { - -} diff --git a/extensions/src/ACRE2TS/CommandServer.h b/extensions/src/ACRE2TS/CommandServer.h index a927a873a..97d126571 100644 --- a/extensions/src/ACRE2TS/CommandServer.h +++ b/extensions/src/ACRE2TS/CommandServer.h @@ -9,20 +9,25 @@ #include "Engine.h" #include "Lockable.h" -class CCommandServer : public IServer, public CLockable -{ +class CCommandServer : public IServer, public CLockable { public: - CCommandServer(void); + CCommandServer(); CCommandServer(const acre::id_t id); - ~CCommandServer(void); + ~CCommandServer() = default; - acre::Result initialize(void); - acre::Result shutdown(void); + acre::Result initialize(); + acre::Result shutdown(); acre::Result sendMessage(IMessage *msg); acre::Result handleMessage(unsigned char *msg); + acre::Result handleMessage(unsigned char *msg, size_t length) override { + (void)msg; + (void)length; - acre::Result release(void); + return acre::Result::notImplemented; + } + + acre::Result release(); inline void setCommandId(char *const value) { m_commandId = value; } inline char* getCommandId() const { return m_commandId; } diff --git a/extensions/src/ACRE2TS/TS3Client.cpp b/extensions/src/ACRE2TS/TS3Client.cpp index fa8ffb717..008a0afa9 100644 --- a/extensions/src/ACRE2TS/TS3Client.cpp +++ b/extensions/src/ACRE2TS/TS3Client.cpp @@ -1,20 +1,12 @@ -#include "compat.h" - -#include "TS3Client.h" +#include "AcreSettings.h" #include "Engine.h" -#include "Types.h" +#include "Log.h" +#include "Shlwapi.h" +#include "TS3Client.h" #include "TsFunctions.h" +#include "Types.h" +#include "compat.h" #include "shlobj.h" -#include "Shlwapi.h" -#include "Log.h" -#include -#include -#include -#include -#include -#include - -#include "AcreSettings.h" #pragma comment(lib, "Shlwapi.lib") @@ -23,10 +15,10 @@ extern TS3Functions ts3Functions; -//TS3Functions CTS3Client::ts3Functions; +// TS3Functions CTS3Client::ts3Functions; acre::Result CTS3Client::initialize(void) { - setPreviousTSChannel(INVALID_TS3_CHANNEL); + setPreviousChannel(INVALID_TS3_CHANNEL); return acre::Result::ok; } @@ -47,12 +39,10 @@ acre::Result CTS3Client::setMuted(const acre::id_t id_, const bool muted_) { } acre::Result CTS3Client::setMuted(std::list idList_, bool muted_) { - return acre::Result::ok; } acre::Result CTS3Client::getMuted(acre::id_t id_) { - return acre::Result::ok; } @@ -64,7 +54,6 @@ acre::Result CTS3Client::stop() { this->m_versionThreadHandle.join(); } this->setState(acre::State::stopped); - } return acre::Result::ok; } @@ -88,19 +77,17 @@ acre::Result CTS3Client::start(const acre::id_t id_) { return acre::Result::ok; } -acre::Result CTS3Client::exPersistVersion( void ) { - +acre::Result CTS3Client::exPersistVersion(void) { CTS3Client::setClientMetadata(ACRE_VERSION_METADATA); ts3Functions.printMessageToCurrentTab("ACRE2 loaded and initialized"); ts3Functions.printMessageToCurrentTab(ACRE_VERSION_METADATA); - clock_t run = clock() / CLOCKS_PER_SEC; + clock_t run = clock() / CLOCKS_PER_SEC; clock_t delta = run; while (this->getState() == acre::State::running && CEngine::getInstance()->getExternalServer()) { - delta = (clock() / CLOCKS_PER_SEC) - run; - if (delta > (PERSIST_VERSION_TIMER / 1000) ) { + if (delta > (PERSIST_VERSION_TIMER / 1000)) { char selfVariableBuffer[4096]; if (CEngine::getInstance()->getGameServer()->getConnected()) { _snprintf_s(selfVariableBuffer, 4094, "%s\nArma Connected: Yes", ACRE_VERSION_METADATA); @@ -143,7 +130,7 @@ acre::Result CTS3Client::setClientMetadata(const char *const data) { bool CTS3Client::getVAD() { char *data; bool returnValue = false; - uint32_t res = ts3Functions.getPreProcessorConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), "vad", &data); + uint32_t res = ts3Functions.getPreProcessorConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), "vad", &data); if (!res) { if (!strcmp(data, "true")) { returnValue = true; @@ -186,7 +173,7 @@ acre::Result CTS3Client::localStartSpeaking(const acre::Speaking speakingType_, } else { stopDirectSpeaking = true; } - } else if (this->getVAD() && (this->getTsSpeakingState() == STATUS_TALKING)) { + } else if (this->getVAD() && (this->getSpeakingState() == STATUS_TALKING)) { stopDirectSpeaking = true; } } @@ -241,7 +228,7 @@ acre::Result CTS3Client::localStopSpeaking(const acre::Speaking speakingType_) { } } else { this->setOnRadio(false); - if (this->getTsSpeakingState() == STATUS_TALKING) { + if (this->getSpeakingState() == STATUS_TALKING) { resendDirectSpeaking = true; } } @@ -252,8 +239,8 @@ acre::Result CTS3Client::localStopSpeaking(const acre::Speaking speakingType_) { } else { resendDirectSpeaking = true; } - } else if (this->getTsSpeakingState() == STATUS_TALKING) { - resendDirectSpeaking = true; + } else if (this->getSpeakingState() == STATUS_TALKING) { + resendDirectSpeaking = true; } } @@ -271,9 +258,10 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { if (currentStatus != status_) { uint32_t res = 0u; if (status_) { - res = ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_NONE); + res = ts3Functions.setClientSelfVariableAsInt( + ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_NONE); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error toggling microphone enabled: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -281,9 +269,10 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { return acre::Result::ok; } } else { - res = ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_MUTED); + res = ts3Functions.setClientSelfVariableAsInt( + ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, MUTEINPUT_MUTED); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error, failed to disable microphone input: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -293,7 +282,7 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { } res = ts3Functions.flushClientSelfUpdates(ts3Functions.getCurrentServerConnectionHandlerID(), NULL); if (!((res == ERROR_ok) || (res == ERROR_ok_no_update))) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("STOP TALKING: Error flushing after toggling microphone muted: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -305,11 +294,11 @@ acre::Result CTS3Client::enableMicrophone(const bool status_) { } bool CTS3Client::getInputStatus() { - bool status = false; - int32_t ret = 0u; + bool status = false; + int32_t ret = 0u; uint32_t res = ts3Functions.getClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_MUTED, &ret); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error querying microphone input status: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -325,16 +314,16 @@ bool CTS3Client::getInputStatus() { } acre::Result CTS3Client::playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_) { - if (!PathFileExistsA(path_.c_str())) { return acre::Result::error; } char soundpackDb[32]; uint32_t ret = 0u; - uint32_t res = ts3Functions.getClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_OUTPUT_MUTED, (int32_t *) &ret); + uint32_t res = + ts3Functions.getClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_OUTPUT_MUTED, (int32_t *) &ret); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("Error checking playback status: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -347,31 +336,25 @@ acre::Result CTS3Client::playSound(std::string path_, acre::vec3_fp32_t position } // create a volume ranged from -40 to 0dB change - _snprintf_s(soundpackDb, 32, "%f", (-40.0f + (40.0f * volume_) ) ); + _snprintf_s(soundpackDb, 32, "%f", (-40.0f + (40.0f * volume_))); // change the soundpack volume for this squawks volume - ts3Functions.setPlaybackConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), - "volume_factor_wave", - soundpackDb); + ts3Functions.setPlaybackConfigValue(ts3Functions.getCurrentServerConnectionHandlerID(), "volume_factor_wave", soundpackDb); TS3_VECTOR vector = {position_.x, position_.z, position_.y}; TRACE("HIT [%f,%f,%f]", vector.x, vector.z, vector.y); uint64_t playHandle; - ret = ts3Functions.playWaveFileHandle(ts3Functions.getCurrentServerConnectionHandlerID(), - path_.c_str(), - looping_, - &playHandle); - ret = ts3Functions.set3DWaveAttributes(ts3Functions.getCurrentServerConnectionHandlerID(), - playHandle, - &vector); + ret = ts3Functions.playWaveFileHandle(ts3Functions.getCurrentServerConnectionHandlerID(), path_.c_str(), looping_, &playHandle); + ret = ts3Functions.set3DWaveAttributes(ts3Functions.getCurrentServerConnectionHandlerID(), playHandle, &vector); return acre::Result::ok; } -std::string CTS3Client::getUniqueId( ) { +std::string CTS3Client::getUniqueId() { char *uniqueId; std::string serverUniqueId = ""; - uint32_t res = ts3Functions.getServerVariableAsString(ts3Functions.getCurrentServerConnectionHandlerID(), VIRTUALSERVER_UNIQUE_IDENTIFIER, &uniqueId); + uint32_t res = ts3Functions.getServerVariableAsString( + ts3Functions.getCurrentServerConnectionHandlerID(), VIRTUALSERVER_UNIQUE_IDENTIFIER, &uniqueId); if (res == ERROR_ok) { serverUniqueId = std::string(uniqueId); if (uniqueId) { @@ -397,7 +380,7 @@ std::string CTS3Client::getConfigFilePath(void) { return tempFolder; } -std::string CTS3Client::getTempFilePath( void ) { +std::string CTS3Client::getTempFilePath(void) { char tempPath[MAX_PATH - 14]; GetTempPathA(sizeof(tempPath), tempPath); std::string tempFolder = std::string(tempPath); @@ -420,9 +403,10 @@ acre::Result CTS3Client::microphoneOpen(bool status_) { this->setInputActive(false); } - uint32_t res = ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_DEACTIVATED, micStatus); + uint32_t res = + ts3Functions.setClientSelfVariableAsInt(ts3Functions.getCurrentServerConnectionHandlerID(), CLIENT_INPUT_DEACTIVATED, micStatus); if (res != ERROR_ok) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("STOP TALKING: Error toggling push-to-talk: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -432,7 +416,7 @@ acre::Result CTS3Client::microphoneOpen(bool status_) { res = ts3Functions.flushClientSelfUpdates(ts3Functions.getCurrentServerConnectionHandlerID(), NULL); if (!(res == ERROR_ok || res == ERROR_ok_no_update)) { - char* errorMsg; + char *errorMsg; if (ts3Functions.getErrorMessage(res, &errorMsg) == ERROR_ok) { LOG("STOP TALKING: Error flushing after toggling push-to-talk: %s\n", errorMsg); ts3Functions.freeMemory(errorMsg); @@ -442,67 +426,67 @@ acre::Result CTS3Client::microphoneOpen(bool status_) { return acre::Result::ok; } -acre::Result CTS3Client::unMuteAll( void ) { +acre::Result CTS3Client::unMuteAll(void) { anyID clientId; anyID *clientList; - uint32_t total_retries = 0; + uint32_t total_retries = 0; uint32_t total_intentional_runs = 0; - //for (total_intentional_runs = 0; total_intentional_runs < 3; total_intentional_runs++) { - uint32_t res = ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId); - if (res == ERROR_ok) { - - res = ERROR_undefined; - for (total_retries = 0; (total_retries < 5) && (res != ERROR_ok); total_retries++) { - res = ts3Functions.getClientList(ts3Functions.getCurrentServerConnectionHandlerID(), &clientList); - if (res == ERROR_ok) { - res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); - //if (res != ERROR_ok) { - // Sleep(500 * total_retries); - //} - ts3Functions.freeMemory(clientList); - } - } - - /* - /* - This was the alternative method originally, but it was hitting the spam threshold */ - /* Disable this method + // for (total_intentional_runs = 0; total_intentional_runs < 3; total_intentional_runs++) { + uint32_t res = ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId); + if (res == ERROR_ok) { + res = ERROR_undefined; + for (total_retries = 0; (total_retries < 5) && (res != ERROR_ok); total_retries++) { res = ts3Functions.getClientList(ts3Functions.getCurrentServerConnectionHandlerID(), &clientList); if (res == ERROR_ok) { - for (x=0;clientList[x]!=0 && total_retries < 20;x++) { - anyID tempList[2]; - uint32_t tries_on_client; - - tempList[0] = (anyID)clientList[x]; - tempList[1] = 0x0000; - - res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); - for (tries_on_client = 0; tries_on_client < 5 && total_retries < 20 && res != ERROR_ok; tries_on_client++, total_retries++) { - res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), tempList, NULL); - if (res != ERROR_ok) { - Sleep(500 * tries_on_client); - } - } - } + res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); + // if (res != ERROR_ok) { + // Sleep(500 * total_retries); + //} ts3Functions.freeMemory(clientList); } - */ } + + /* + /* - This was the alternative method originally, but it was hitting the spam threshold */ + /* Disable this method + res = ts3Functions.getClientList(ts3Functions.getCurrentServerConnectionHandlerID(), &clientList); + if (res == ERROR_ok) { + for (x=0;clientList[x]!=0 && total_retries < 20;x++) { + anyID tempList[2]; + uint32_t tries_on_client; + + tempList[0] = (anyID)clientList[x]; + tempList[1] = 0x0000; + + res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), clientList, NULL); + for (tries_on_client = 0; tries_on_client < 5 && total_retries < 20 && res != ERROR_ok; tries_on_client++, total_retries++) + { res = ts3Functions.requestUnmuteClients(ts3Functions.getCurrentServerConnectionHandlerID(), tempList, NULL); if (res != ERROR_ok) + { Sleep(500 * tries_on_client); + } + } + } + ts3Functions.freeMemory(clientList); + } + */ + } // Sleep(500); //} return acre::Result::ok; } -acre::Result CTS3Client::moveToServerTS3Channel() { - if (!CAcreSettings::getInstance()->getDisableTS3ChannelSwitch()) { +acre::Result CTS3Client::moveToServerChannel() { + if (!CAcreSettings::getInstance()->getDisableChannelSwitch()) { anyID clientId; - std::vector details = getTs3ChannelDetails(); + std::vector details = getChannelDetails(); if (ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId) == ERROR_ok) { uint64_t currentChannelId = INVALID_TS3_CHANNEL; - if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == ERROR_ok && getPreviousTSChannel() == INVALID_TS3_CHANNEL) { - setPreviousTSChannel(currentChannelId); + if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == + ERROR_ok && + getPreviousChannel() == INVALID_TS3_CHANNEL) { + setPreviousChannel(currentChannelId); } const uint64_t channelId = findChannelByNames(details); @@ -511,27 +495,29 @@ acre::Result CTS3Client::moveToServerTS3Channel() { if (details.at(1) != "" && details.at(0) != "") { password = details.at(1); } - ts3Functions.requestClientMove(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, channelId, password.c_str(), nullptr); + ts3Functions.requestClientMove( + ts3Functions.getCurrentServerConnectionHandlerID(), clientId, channelId, password.c_str(), nullptr); } } } - setShouldSwitchTS3Channel(false); + setShouldSwitchChannel(false); return acre::Result::ok; } -acre::Result CTS3Client::moveToPreviousTS3Channel() { - if (!CAcreSettings::getInstance()->getDisableTS3ChannelSwitch()) { +acre::Result CTS3Client::moveToPreviousChannel() { + if (!CAcreSettings::getInstance()->getDisableChannelSwitch()) { anyID clientId; if (ts3Functions.getClientID(ts3Functions.getCurrentServerConnectionHandlerID(), &clientId) == ERROR_ok) { uint64_t currentChannelId = INVALID_TS3_CHANNEL; - if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == ERROR_ok) { - const uint64_t channelId = getPreviousTSChannel(); + if (ts3Functions.getChannelOfClient(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, ¤tChannelId) == + ERROR_ok) { + const uint64_t channelId = getPreviousChannel(); if (channelId != INVALID_TS3_CHANNEL && channelId != currentChannelId) { ts3Functions.requestClientMove(ts3Functions.getCurrentServerConnectionHandlerID(), clientId, channelId, "", nullptr); } } } - setPreviousTSChannel(INVALID_TS3_CHANNEL); + setPreviousChannel(INVALID_TS3_CHANNEL); } return acre::Result::ok; } @@ -539,7 +525,7 @@ acre::Result CTS3Client::moveToPreviousTS3Channel() { uint64_t CTS3Client::findChannelByNames(std::vector details_) { uint64_t *channelList; if (ts3Functions.getChannelList(ts3Functions.getCurrentServerConnectionHandlerID(), &channelList) == ERROR_ok) { - uint64_t channelId = INVALID_TS3_CHANNEL; + uint64_t channelId = INVALID_TS3_CHANNEL; uint64_t defaultChannelId = INVALID_TS3_CHANNEL; std::map channelMap; std::string name = details_.at(2); @@ -549,8 +535,9 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { while (*channelList) { channelId = *channelList; channelList++; - char* channelName; - if (ts3Functions.getChannelVariableAsString(ts3Functions.getCurrentServerConnectionHandlerID(), channelId, CHANNEL_NAME, &channelName) == ERROR_ok) { + char *channelName; + if (ts3Functions.getChannelVariableAsString( + ts3Functions.getCurrentServerConnectionHandlerID(), channelId, CHANNEL_NAME, &channelName) == ERROR_ok) { std::string channelNameString = std::string(channelName); if (channelNameString.find(DEFAULT_TS3_CHANNEL) != -1 || (details_.at(0) != "" && channelNameString == name)) { if (channelNameString == DEFAULT_TS3_CHANNEL) { @@ -562,9 +549,9 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { } uint64_t bestChannelId = INVALID_TS3_CHANNEL; - int32_t bestMatches = 0; - int32_t bestDistance = 10; - for (auto& element : channelMap) { + int32_t bestMatches = 0; + int32_t bestDistance = 10; + for (auto &element : channelMap) { std::string fullChannelName = element.second; // Full comparison if (fullChannelName.compare(name) == 0) { @@ -575,20 +562,20 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { // Word comparison const int32_t matches = getWordMatches(cleanChannelName, name); if (matches > bestMatches) { - bestMatches = matches; + bestMatches = matches; bestChannelId = element.first; continue; } // Char comparison const int32_t distance = levenshteinDistance(cleanChannelName, name); if (distance <= bestDistance) { - bestDistance = distance; + bestDistance = distance; bestChannelId = element.first; } } if (bestChannelId == INVALID_TS3_CHANNEL) { - if (details_.at(0) != "") { - details_.at(0) = ""; + if (!details_.at(0).empty()) { + details_.at(0).clear(); bestChannelId = findChannelByNames(details_); } else if (defaultChannelId != INVALID_TS3_CHANNEL) { bestChannelId = defaultChannelId; @@ -599,79 +586,19 @@ uint64_t CTS3Client::findChannelByNames(std::vector details_) { return INVALID_TS3_CHANNEL; } -unsigned int CTS3Client::getWordMatches(const std::string& string1_, const std::string& string2_) { - std::vector words1, words2; - std::string temp; - std::stringstream stringstream1(string1_); - while (stringstream1 >> temp) { - words1.push_back(temp); - } - std::stringstream stringstream2(string2_); - while (stringstream2 >> temp) { - words2.push_back(temp); - } - - int32_t matches = 0; - for (auto& word1 : words1) { - for (auto& word2 : words2) { - if (word1 == word2) { - matches++; - } - } - } - return matches; -} - -uint32_t CTS3Client::levenshteinDistance(const std::string& string1_, const std::string& string2_) { - int32_t length1 = string1_.size(); - const int32_t length2 = string2_.size(); - - const decltype(length1) columnStart = decltype(length1)(1); - - decltype(length1)*const column = new decltype(length1)[length1 + 1]; - std::iota(column + columnStart, column + length1 + 1, columnStart); - - for (auto x = columnStart; x <= length2; x++) { - column[0] = x; - int32_t lastDiagonal = x - columnStart; - for (auto y = columnStart; y <= length1; y++) { - const int32_t oldDiagonal = column[y]; - const std::initializer_list possibilities = { - column[y] + 1, - column[y - 1] + 1, - lastDiagonal + (string1_[y - 1] == string2_[x - 1] ? 0 : 1) - }; - column[y] = min(possibilities); - lastDiagonal = oldDiagonal; - } - } - const int32_t result = column[length1]; - delete[] column; - return result; -} - -std::string CTS3Client::removeSubstrings(std::string string_, std::string substring_) { - const std::string::size_type substringLength = substring_.length(); - for (auto iterator = string_.find(substring_); - iterator != std::string::npos; - iterator = string_.find(substring_)) - string_.erase(iterator, substringLength); - return string_; -} - -acre::Result CTS3Client::updateTs3ChannelDetails(std::vector details_) { - setTs3ChannelDetails(details_); +acre::Result CTS3Client::updateChannelDetails(std::vector details_) { + setChannelDetails(details_); if (!details_.empty()) { - updateShouldSwitchTS3Channel(true); + updateShouldSwitchChannel(true); } return acre::Result::ok; } -acre::Result CTS3Client::updateShouldSwitchTS3Channel(const bool state_) { - setShouldSwitchTS3Channel(state_); +acre::Result CTS3Client::updateShouldSwitchChannel(const bool state_) { + setShouldSwitchChannel(state_); return acre::Result::ok; } -bool CTS3Client::shouldSwitchTS3Channel() { - return getShouldSwitchTS3Channel(); +bool CTS3Client::shouldSwitchChannel() { + return getShouldSwitchChannel(); } diff --git a/extensions/src/ACRE2TS/TS3Client.h b/extensions/src/ACRE2TS/TS3Client.h index 5a2cce9c2..18e75951f 100644 --- a/extensions/src/ACRE2TS/TS3Client.h +++ b/extensions/src/ACRE2TS/TS3Client.h @@ -2,54 +2,56 @@ #include "IClient.h" #include "TsFunctions.h" -#include + #include +#include #include -class CTS3Client: public IClient { +class CTS3Client : public IClient { public: + // static TS3Functions ts3Functions; - //static TS3Functions ts3Functions; + CTS3Client() = default; + ~CTS3Client() final = default; - CTS3Client() { }; - ~CTS3Client() { }; + acre::Result initialize(void) final; - acre::Result initialize( void ); + acre::Result setMuted(const acre::id_t id_, const bool muted_) final; + acre::Result setMuted(std::list idList_, const bool muted_) final; - acre::Result setMuted(const acre::id_t id_, const bool muted_); - acre::Result setMuted(std::list idList_, const bool muted_); + acre::Result getMuted(const acre::id_t id_) final; - acre::Result getMuted(const acre::id_t id_); + acre::Result stop() final; + acre::Result start(const acre::id_t id_) final; - acre::Result stop(); - acre::Result start(const acre::id_t id_); - - acre::Result exPersistVersion( void ); + acre::Result exPersistVersion(void); acre::Result setClientMetadata(const char *const data); - acre::Result enableMicrophone(const bool status_); + acre::Result enableMicrophone(const bool status_) final; bool getInputStatus(); + bool getVAD(); + /*! - * \brief Handles local player starting speaking. - * - * \param[in] speakingType_ ACRE speaking type - * - * \return acre::Result::ok if operation successful - */ - acre::Result localStartSpeaking(const acre::Speaking speakingType_); + * \brief Handles local player starting speaking. + * + * \param[in] speakingType_ ACRE speaking type + * + * \return acre::Result::ok if operation successful + */ + acre::Result localStartSpeaking(const acre::Speaking speakingType_) final; /*! - * \brief Handles local player starting speaking. - * - * \param[in] speakingType_ ACRE speaking type - * \param[in] radioId_ Unique radio ideintifier - * - * \return acre::Result::ok if operation successful - */ - acre::Result localStartSpeaking(const acre::Speaking speakingType_, std::string radioId_); + * \brief Handles local player starting speaking. + * + * \param[in] speakingType_ ACRE speaking type + * \param[in] radioId_ Unique radio ideintifier + * + * \return acre::Result::ok if operation successful + */ + acre::Result localStartSpeaking(const acre::Speaking speakingType_, std::string radioId_) final; /*! * \brief Handles local player stopping speaking. @@ -58,54 +60,28 @@ class CTS3Client: public IClient { * * \return acre::Result::ok if operation successful */ - acre::Result localStopSpeaking(const acre::Speaking speakingType_ ); + acre::Result localStopSpeaking(const acre::Speaking speakingType_) final; - std::string getTempFilePath( void ); - std::string getConfigFilePath(void); + std::string getTempFilePath(void) final; + std::string getConfigFilePath(void) final; - acre::Result playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_); + acre::Result playSound(std::string path_, acre::vec3_fp32_t position_, const float32_t volume_, const int32_t looping_) final; - std::string getUniqueId( ); + std::string getUniqueId() final; - bool getVAD(); + acre::Result microphoneOpen(const bool status_) final; - acre::Result microphoneOpen(const bool status_); - - acre::Result unMuteAll( void ); - - acre::Result moveToServerTS3Channel(); - acre::Result moveToPreviousTS3Channel(); - uint64 findChannelByNames(std::vector details_); - uint32_t getWordMatches(const std::string& string1_, const std::string& string2_); - uint32_t levenshteinDistance(const std::string& string1_, const std::string& string2_); - std::string removeSubstrings(std::string string_, std::string substring_); - acre::Result updateTs3ChannelDetails(std::vector details_); - acre::Result updateShouldSwitchTS3Channel(const bool state_); - bool shouldSwitchTS3Channel(); - - - inline void setState(acre::State value) final { m_state = value; } - inline acre::State getState() const final { return m_state; } - - DECLARE_MEMBER(bool, hadVAD); - DECLARE_MEMBER(bool, InputActive); - DECLARE_MEMBER(bool, OnRadio); - DECLARE_MEMBER(int32_t, TsSpeakingState); - DECLARE_MEMBER(bool, RadioPTTDown); - DECLARE_MEMBER(bool, IntercomPTTDown); - DECLARE_MEMBER(bool, GodPTTDown); - DECLARE_MEMBER(bool, ZeusPTTDown); - DECLARE_MEMBER(bool, MainPTTDown); - DECLARE_MEMBER(bool, DirectFirst); - DECLARE_MEMBER(bool, HitTSSpeakingEvent); - DECLARE_MEMBER(bool, IsX3DInitialized); - DECLARE_MEMBER(uint32_t, SpeakerMask); - DECLARE_MEMBER(uint64, PreviousTSChannel); - DECLARE_MEMBER(std::vector, Ts3ChannelDetails); - DECLARE_MEMBER(bool, ShouldSwitchTS3Channel) -protected: - std::thread m_versionThreadHandle; - char *m_vadLevel; + acre::Result unMuteAll(void) final; + + acre::Result moveToServerChannel() final; + acre::Result moveToPreviousChannel() final; + uint64 findChannelByNames(std::vector details_) final; - acre::State m_state; + acre::Result updateChannelDetails(std::vector details_) final; + acre::Result updateShouldSwitchChannel(const bool state_) final; + bool shouldSwitchChannel() final; + +private: + std::thread m_versionThreadHandle; + char *m_vadLevel = nullptr; }; diff --git a/extensions/src/ACRE2TS/TsCallbacks_channelEvents.cpp b/extensions/src/ACRE2TS/TsCallbacks_channelEvents.cpp index 4c9d4827a..b76638e09 100644 --- a/extensions/src/ACRE2TS/TsCallbacks_channelEvents.cpp +++ b/extensions/src/ACRE2TS/TsCallbacks_channelEvents.cpp @@ -82,5 +82,5 @@ void ts3plugin_onClientMoveMovedEvent(uint64 serverConnectionHandlerID, anyID cl } void ts3plugin_onUpdateChannelEditedEvent(uint64 serverConnectionHandlerID, uint64 channelID, anyID invokerID, const char* invokerName, const char* invokerUniqueIdentifier) { - CEngine::getInstance()->getClient()->updateShouldSwitchTS3Channel(true); + CEngine::getInstance()->getClient()->updateShouldSwitchChannel(true); } diff --git a/extensions/src/ACRE2TS/TsCallbacks_speaking.cpp b/extensions/src/ACRE2TS/TsCallbacks_speaking.cpp index 94e75150d..aaefa24cb 100644 --- a/extensions/src/ACRE2TS/TsCallbacks_speaking.cpp +++ b/extensions/src/ACRE2TS/TsCallbacks_speaking.cpp @@ -16,8 +16,8 @@ #include "TS3Client.h" // -// TS3 Speaking callbacks -// +// TS3 Speaking callbacks +// void ts3plugin_onTalkStatusChangeEvent(uint64 serverConnectionHandlerID, int status, int isReceivedWhisper, anyID clientID) { if (static_cast(clientID) != CEngine::getInstance()->getSelf()->getId()) { @@ -32,10 +32,10 @@ void ts3plugin_onTalkStatusChangeEvent(uint64 serverConnectionHandlerID, int sta return; } - ((CTS3Client *) (CEngine::getInstance()->getClient()))->setTsSpeakingState(status); + ((CTS3Client *) (CEngine::getInstance()->getClient()))->setSpeakingState(status); if (CEngine::getInstance()->getSoundSystemOverride()) { return; - } else if ((((CTS3Client *) (CEngine::getInstance()->getClient()))->getOnRadio())) { + } else if (((CTS3Client *) (CEngine::getInstance()->getClient()))->getOnRadio()) { if (((CTS3Client *) (CEngine::getInstance()->getClient()))->getVAD()) { return; } else { diff --git a/extensions/src/ACRE2TS/acre2_ts_dllmain.cpp b/extensions/src/ACRE2TS/acre2_ts_dllmain.cpp index 48e1cd710..33c9aa38f 100644 --- a/extensions/src/ACRE2TS/acre2_ts_dllmain.cpp +++ b/extensions/src/ACRE2TS/acre2_ts_dllmain.cpp @@ -16,9 +16,9 @@ BOOL WINAPI DllMain(HINSTANCE hInst,DWORD reason,LPVOID) { if (reason == DLL_PROCESS_ATTACH){ //g_Log = (Log *)new Log("acre.log"); } - if (reason == DLL_PROCESS_DETACH) { - + if (reason == DLL_PROCESS_DETACH) { + } return 1; } -*/ \ No newline at end of file +*/ diff --git a/tools/make.py b/tools/make.py index b090667f1..8575c015c 100755 --- a/tools/make.py +++ b/tools/make.py @@ -74,10 +74,10 @@ signature_blacklist = [] importantFiles = ["acre_logo_medium_ca.paa", "meta.cpp", "mod.cpp", "LICENSE", "README.md", "acre.dll", "acre_x64.dll", "ACRE2Arma.dll", "ACRE2Arma_x64.dll", "ACRE2Steam.dll", "ACRE2Steam_x64.dll"] extrasFiles = ["examples", "Wav2B64.exe"] -pluginFiles = ["acre2_win32.dll", "acre2_win64.dll"] +pluginFiles = ["ts3", "mumble"] versionFiles = ["README.md", "extensions\\src\\ACRE2Shared\\version.h", "docs\\_data\\sidebar.yml"] -extensions32 = ["ACRE2Arma\\acre", "ACRE2Arma\\arma2ts", "ACRE2\\ACRE2Steam", "ACRE2\\ACRE2TS", "Extras\\Wav2B64"] -extensions64 = ["ACRE2Arma\\acre", "ACRE2Arma\\arma2ts", "ACRE2\\ACRE2Steam", "ACRE2\\ACRE2TS"] +extensions32 = ["ACRE2Arma\\acre", "ACRE2Arma\\arma2ts", "ACRE2\\ACRE2Steam", "ACRE2\\ACRE2TS", "ACRE2\\ACRE2Mumble", "Extras\\Wav2B64"] +extensions64 = ["ACRE2Arma\\acre", "ACRE2Arma\\arma2ts", "ACRE2\\ACRE2Steam", "ACRE2\\ACRE2TS", "ACRE2\\ACRE2Mumble"] # be_cred_file expected to be in folder defined by enviorment variable CBA_PUBLISH_CREDENTIALS_PATH be_cred_filename = "acre_battleye_creds.json" @@ -434,8 +434,12 @@ def copy_important_files(source_dir,destination_dir): for file in pluginFiles: filePath = os.path.join(source_plugin_dir, file) if os.path.exists(filePath): - print_green("Copying plugin dll => {}".format(filePath)) - shutil.copy(filePath, destination_plugin_dir) + if os.path.isdir(filePath): + print_green("Copying plugin directory => {}".format(filePath)) + shutil.copytree(filePath, os.path.join(destination_plugin_dir,file)) + else: + print_green("Copying plugin dll => {}".format(filePath)) + shutil.copy(filePath, destination_plugin_dir) else: missingFiles.append("{}".format(filePath)) print_error("Failed copying plugin dll => {}".format(filePath))