diff --git a/code/components/extra-natives-five/src/NuiAudioSink.cpp b/code/components/extra-natives-five/src/NuiAudioSink.cpp index 22c7da972d..2217a8e3af 100644 --- a/code/components/extra-natives-five/src/NuiAudioSink.cpp +++ b/code/components/extra-natives-five/src/NuiAudioSink.cpp @@ -1307,7 +1307,7 @@ extern "C" class MumbleAudioEntityBase { public: - MumbleAudioEntityBase(const std::wstring& name) + MumbleAudioEntityBase(const std::string& name) : m_position(rage::Vec3V{ 0.f, 0.f, 0.f }), m_positionForce(rage::Vec3V{ 0.f, 0.f, 0.f }), m_buffer(nullptr), @@ -1389,7 +1389,7 @@ class MumbleAudioEntityBase CPed* m_ped; - std::wstring m_name; + std::string m_name; std::function m_poller; }; @@ -1398,7 +1398,7 @@ template class MumbleAudioEntity : public rage::audEntity, public MumbleAudioEntityBase, public std::enable_shared_from_this> { public: - MumbleAudioEntity(const std::wstring& name) + MumbleAudioEntity(const std::string& name) : MumbleAudioEntityBase(name) { } @@ -1821,7 +1821,7 @@ class MumbleAudioSink : public IMumbleAudioSink public: void Process(); - MumbleAudioSink(const std::wstring& name); + MumbleAudioSink(const std::string& name); virtual ~MumbleAudioSink() override; virtual void SetPollHandler(const std::function& poller) override; @@ -1833,7 +1833,7 @@ class MumbleAudioSink : public IMumbleAudioSink void Reset(); private: - std::wstring m_name; + std::string m_name; int m_serverId; /// @@ -1860,11 +1860,9 @@ static std::set g_sinks; static std::shared_mutex g_submixMutex; static std::map g_submixIds; -MumbleAudioSink::MumbleAudioSink(const std::wstring& name) - : m_serverId(-1), m_position(rage::Vec3V{ 0.f, 0.f, 0.f }), m_distance(5.0f), m_overrideVolume(-1.0f), m_name(name) +MumbleAudioSink::MumbleAudioSink(const std::string& userName) + : m_serverId(-1), m_position(rage::Vec3V{ 0.f, 0.f, 0.f }), m_distance(5.0f), m_overrideVolume(-1.0f), m_name(userName) { - auto userName = ToNarrow(name); - if (userName.length() >= 2) { int serverId = atoi(userName.substr(1, userName.length() - 1).c_str()); @@ -2888,7 +2886,7 @@ static InitFunction initFunction([]() ProcessAudioSinks(); }); - OnGetMumbleAudioSink.Connect([](const std::wstring& name, fwRefContainer* sink) + OnGetMumbleAudioSink.Connect([](const std::string& name, fwRefContainer* sink) { fwRefContainer ref = new MumbleAudioSink(name); *sink = ref; diff --git a/code/components/gta-net-five/src/MumbleVoice.cpp b/code/components/gta-net-five/src/MumbleVoice.cpp index 1773cc998d..ee87f7ed6a 100644 --- a/code/components/gta-net-five/src/MumbleVoice.cpp +++ b/code/components/gta-net-five/src/MumbleVoice.cpp @@ -33,6 +33,8 @@ #include #include +#include "ScriptWarnings.h" + #if __has_include() #include #endif @@ -358,6 +360,16 @@ static float* g_actorPos; #pragma comment(lib, "dsound.lib") +bool IsMumbleConnected() +{ + if (!g_mumble.connectionInfo) + { + return false; + } + + return g_mumble.connected && g_mumble.connectionInfo->isConnected; +} + static void Mumble_RunFrame() { if (!Instance::Get()->HasVariable("gameSettled")) @@ -370,20 +382,15 @@ static void Mumble_RunFrame() return; } - if (!g_mumble.connected || (g_mumble.connectionInfo && !g_mumble.connectionInfo->isConnected)) + if (!IsMumbleConnected()) { if (Mumble_ShouldConnect() && !g_mumble.connecting && !g_mumble.errored) { if (GetTickCount64() > g_mumble.nextConnectAt) { Mumble_Connect(); - - g_mumble.nextConnectDelay *= 2; - - if (g_mumble.nextConnectDelay > 30 * 1000) - { - g_mumble.nextConnectDelay = 30 * 1000; - } + + g_mumble.nextConnectDelay = std::min(g_mumble.nextConnectDelay * 2, 30'000); g_mumble.nextConnectAt = GetTickCount64() + g_mumble.nextConnectDelay; } @@ -802,13 +809,17 @@ static fx::TNativeHandler getServerId; static std::shared_ptr getAudioContext(int playerId) { - if (!g_mumble.connected) + int serverId = FxNativeInvoke::Invoke(getServerId, playerId); + + // if the server id is 0 then we don't have a player. + // WARNING: This is a implementation detail + if (!g_mumble.connected || serverId == 0) { return {}; } std::string name = fmt::sprintf("[%d] %s", - FxNativeInvoke::Invoke(getServerId, playerId), + serverId, FxNativeInvoke::Invoke(getPlayerName, playerId)); return g_mumbleClient->GetAudioContext(name); } @@ -819,18 +830,60 @@ static std::shared_ptr getAudioContextByServerId(int serverId { return {}; } - - std::string name = ToNarrow(g_mumbleClient->GetPlayerNameFromServerId(serverId)); + + std::string name = g_mumbleClient->GetPlayerNameFromServerId(serverId); + if (name.empty()) + { + return {}; + } + return g_mumbleClient->GetAudioContext(name); } -std::wstring getMumbleName(int playerId) +std::optional getMumbleName(int playerId) +{ + int serverId = FxNativeInvoke::Invoke(getServerId, playerId); + + // if the server id is 0 then we don't have a player. + // WARNING: This is a implementation detail + if (serverId == 0) + { + return std::nullopt; + } + + return fmt::sprintf("[%d] %s", + serverId, + FxNativeInvoke::Invoke(getPlayerName, playerId)); +} + + +std::string GetMumbleChannel(int channelId) +{ + return fmt::sprintf("Game Channel %d", channelId); +} + +// Returns true if the voice target id valid to use with the `VoiceTarget` packet (1..30) +bool IsVoiceTargetIdValid(int id) { - return ToWide(fmt::sprintf("[%d] %s", - FxNativeInvoke::Invoke(getServerId, playerId), - FxNativeInvoke::Invoke(getPlayerName, playerId))); + return id >= 1 && id <= 30; } +// Ensures that mumble is connected before calling any mumble related functions +template +inline auto MakeMumbleNative(MumbleFn fn, uintptr_t defaultValue = 0) +{ + return [=](fx::ScriptContext& context) + { + if (!g_mumble.connected) + { + context.SetResult(defaultValue); + return; + } + + fn(context); + }; +}; + #include static HookFunction hookFunction([]() @@ -923,22 +976,18 @@ static HookFunction hookFunction([]() } }); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_TALKER_PROXIMITY", [](fx::ScriptContext& context) + + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_TALKER_PROXIMITY", MakeMumbleNative([](fx::ScriptContext& context) { float proximity = context.GetArgument(0); - if (g_mumble.connected) - { - g_mumbleClient->SetAudioDistance(proximity); - } - }); + g_mumbleClient->SetAudioDistance(proximity); + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_GET_TALKER_PROXIMITY", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_GET_TALKER_PROXIMITY", MakeMumbleNative([](fx::ScriptContext& context) { - float proximity = (g_mumble.connected) ? g_mumbleClient->GetAudioDistance() : 0.0f; - - context.SetResult(proximity); - }); + context.SetResult(g_mumbleClient->GetAudioDistance()); + }, 0.0f)); fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_ACTIVE", [](fx::ScriptContext& context) { @@ -950,313 +999,321 @@ static HookFunction hookFunction([]() context.SetResult(g_voiceActiveByScript); }); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_IS_PLAYER_TALKING", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_IS_PLAYER_TALKING", MakeMumbleNative([](fx::ScriptContext& context) { int playerId = context.GetArgument(0); bool isTalking = false; - if (g_mumble.connected) + if (playerId >= 0 && playerId < g_talkers.size()) { - if (playerId >= 0 && playerId < g_talkers.size()) - { - isTalking = g_talkers.test(playerId); - } + isTalking = g_talkers.test(playerId); } context.SetResult(isTalking); - }); + }, false)); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOLUME_OVERRIDE", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOLUME_OVERRIDE", MakeMumbleNative([](fx::ScriptContext& context) { int playerId = context.GetArgument(0); float volume = context.GetArgument(1); - if (g_mumble.connected) + if (auto name = getMumbleName(playerId)) { - std::wstring name = getMumbleName(playerId); - - g_mumbleClient->SetClientVolumeOverride(name, volume); + g_mumbleClient->SetClientVolumeOverride(*name, volume); } - }); + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOLUME_OVERRIDE_BY_SERVER_ID", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOLUME_OVERRIDE_BY_SERVER_ID", MakeMumbleNative([](fx::ScriptContext& context) { int serverId = context.GetArgument(0); float volume = context.GetArgument(1); - if (g_mumble.connected) - { - g_mumbleClient->SetClientVolumeOverrideByServerId(serverId, volume); - } - }); + g_mumbleClient->SetClientVolumeOverrideByServerId(serverId, volume); + })); static VoiceTargetConfig vtConfigs[31]; - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_TARGET", [](fx::ScriptContext& context) + static auto InvalidTargetIdWarning = [] (const std::string_view& nativeName) + { + fx::scripting::Warningf("mumble", "%s: Tried to use an invalid targetId, the minimum target id is 1, the maximum is 30.", nativeName); + }; + + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_TARGET", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) - { - vtConfigs[id] = {}; - g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); - } + vtConfigs[id] = {}; + g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); } - }); + else + { + InvalidTargetIdWarning("MUMBLE_CLEAR_VOICE_TARGET"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_TARGET_CHANNEL", [](fx::ScriptContext& context) + + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_TARGET_CHANNEL", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); auto channel = context.GetArgument(1); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) - { - auto targetChannel = fmt::sprintf("Game Channel %d", channel); - auto& targets = vtConfigs[id].targets; - targets.remove_if([targetChannel](auto& target) - { - return target.channel == targetChannel; - }); + auto targetChannel = GetMumbleChannel(channel); + auto& targets = vtConfigs[id]; + // we only want to mark the voice target config as pending if we actually modified it + // `erase()` will return `0` if it didn't remove anything or `1` if it did + if (targets.channels.erase(targetChannel)) + { g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); } } - }); + else + { + InvalidTargetIdWarning("MUMBLE_REMOVE_VOICE_TARGET_CHANNEL"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_TARGET_PLAYER", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_TARGET_PLAYER", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); auto playerId = context.GetArgument(1); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) + if (auto targetName = getMumbleName(playerId)) { - std::wstring targetName = getMumbleName(playerId); + auto& targets = vtConfigs[id]; - auto& targets = vtConfigs[id].targets; - targets.remove_if([targetName](auto& target) + // we only want to mark the voice target config as pending if we actually modified it + // `erase()` will return `0` if it didn't remove anything or `1` if it did + if (targets.users.erase(*targetName)) { - return target.users.size() > 0 && std::find(target.users.begin(), target.users.end(), targetName) != target.users.end(); - }); - - g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); + g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); + } } } - }); + else + { + InvalidTargetIdWarning("MUMBLE_REMOVE_VOICE_TARGET_PLAYER"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_TARGET_PLAYER_BY_SERVER_ID", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_TARGET_PLAYER_BY_SERVER_ID", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); auto serverId = context.GetArgument(1); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) - { - VoiceTargetConfig::Target ch; - std::wstring targetName = g_mumbleClient->GetPlayerNameFromServerId(serverId); + std::string targetName = g_mumbleClient->GetPlayerNameFromServerId(serverId); - if (!targetName.empty()) - { - auto& targets = vtConfigs[id].targets; - targets.remove_if([targetName](auto& target) - { - return target.users.size() > 0 && std::find(target.users.begin(), target.users.end(), targetName) != target.users.end(); - }); - } + // if the player doesn't exist then we don't want to update targetting + if (targetName.empty()) + { + return; } + + auto& targets = vtConfigs[id]; - g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); + if (targets.users.erase(targetName)) + { + g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); + } } - }); + else + { + InvalidTargetIdWarning("MUMBLE_REMOVE_VOICE_TARGET_PLAYER_BY_SERVER_ID"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_TARGET_CHANNELS", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_TARGET_CHANNELS", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) - { - auto& targets = vtConfigs[id].targets; - targets.remove_if([](auto& target) - { - return !target.channel.empty(); - }); + auto& targets = vtConfigs[id]; + + targets.channels.clear(); - g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); - } + g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); } - }); + else + { + InvalidTargetIdWarning("MUMBLE_CLEAR_VOICE_TARGET_CHANNELS"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_TARGET_PLAYERS", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_TARGET_PLAYERS", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) - { - auto& targets = vtConfigs[id].targets; - targets.remove_if([](auto& target) - { - return target.users.size() > 0; - }); + auto& targets = vtConfigs[id]; - g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); - } + targets.users.clear(); + + g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); } - }); + else + { + InvalidTargetIdWarning("MUMBLE_CLEAR_VOICE_TARGET_PLAYERS"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_CHANNEL_LISTEN", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_CHANNEL_LISTEN", MakeMumbleNative([](fx::ScriptContext& context) { auto channel = context.GetArgument(0); - if (g_mumble.connected) + const std::string channelName = GetMumbleChannel(channel); + if (g_mumbleClient->DoesChannelExist(channelName)) { - g_mumbleClient->AddListenChannel(fmt::sprintf("Game Channel %d", channel)); + g_mumbleClient->AddListenChannel(channelName); } - }); - - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_CHANNEL_LISTEN", [](fx::ScriptContext& context) + // TODO: See if we actually want to have a warning here, since this wouldn't cause any noticeable issues, unlike voice targeting which players might + // get confused on why it doesn't work + // else + // { + // fx::scripting::Warningf("mumble", "MUMBLE_ADD_VOICE_CHANNEL_LISTEN: Tried to call native on a channel that didn't exist"); + // } + })); + + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_REMOVE_VOICE_CHANNEL_LISTEN", MakeMumbleNative([](fx::ScriptContext& context) { auto channel = context.GetArgument(0); - if (g_mumble.connected) - { - g_mumbleClient->RemoveListenChannel(fmt::sprintf("Game Channel %d", channel)); - } - }); + g_mumbleClient->RemoveListenChannel(GetMumbleChannel(channel)); + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_TARGET_CHANNEL", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_TARGET_CHANNEL", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); auto channel = context.GetArgument(1); - if (id >= 0 && id < 31) - { - if (g_mumble.connected) - { - VoiceTargetConfig::Target ch; - ch.channel = fmt::sprintf("Game Channel %d", channel); - - vtConfigs[id].targets.push_back(ch); - g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); - } - } - }); - - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_DOES_CHANNEL_EXIST", [](fx::ScriptContext& context) { - auto channel = context.GetArgument(0); - - if (g_mumble.connected) + if (IsVoiceTargetIdValid(id)) { - auto channelName = fmt::sprintf("Game Channel %d", channel); - context.SetResult(g_mumbleClient->DoesChannelExist(channelName)); + auto& targets = vtConfigs[id]; + + targets.channels.emplace(GetMumbleChannel(channel)); + + g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); } else { - context.SetResult(false); + InvalidTargetIdWarning("MUMBLE_ADD_VOICE_TARGET_CHANNEL"); } - }); + })); + + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_DOES_CHANNEL_EXIST", MakeMumbleNative([](fx::ScriptContext& context) { + auto channel = context.GetArgument(0); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_TARGET_PLAYER", [](fx::ScriptContext& context) + context.SetResult(g_mumbleClient->DoesChannelExist(GetMumbleChannel(channel))); + })); + + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_TARGET_PLAYER", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); auto playerId = context.GetArgument(1); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) + auto& targets = vtConfigs[id]; + if (auto name = getMumbleName(playerId)) { - VoiceTargetConfig::Target ch; - std::wstring name = getMumbleName(playerId); - - ch.users.push_back(name); - - vtConfigs[id].targets.push_back(ch); + targets.users.emplace(*name); g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); } } - }); + else + { + InvalidTargetIdWarning("MUMBLE_ADD_VOICE_TARGET_PLAYER"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_TARGET_PLAYER_BY_SERVER_ID", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_ADD_VOICE_TARGET_PLAYER_BY_SERVER_ID", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); int serverId = context.GetArgument(1); - if (id >= 0 && id < 31) + if (IsVoiceTargetIdValid(id)) { - if (g_mumble.connected) - { - VoiceTargetConfig::Target ch; - std::wstring name = g_mumbleClient->GetPlayerNameFromServerId(serverId); - - if (!name.empty()) - { - ch.users.push_back(name); + std::string name = g_mumbleClient->GetPlayerNameFromServerId(serverId); - vtConfigs[id].targets.push_back(ch); - g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); - } + if (!name.empty()) + { + vtConfigs[id].users.emplace(name); + g_mumbleClient->UpdateVoiceTarget(id, vtConfigs[id]); } } - }); + else + { + InvalidTargetIdWarning("MUMBLE_ADD_VOICE_TARGET_PLAYER_BY_SERVER_ID"); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOICE_TARGET", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOICE_TARGET", MakeMumbleNative([](fx::ScriptContext& context) { auto id = context.GetArgument(0); + // We can set our voice target to 0..31 here (and only here!) if (id >= 0 && id < 31) { - if (g_mumble.connected) - { - g_mumbleClient->SetVoiceTarget(id); - } + g_mumbleClient->SetVoiceTarget(id); } - }); + else + { + fx::scripting::Warningf("mumble", "Invalid voice target id %d", id); + } + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_GET_VOICE_CHANNEL_FROM_SERVER_ID", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_GET_VOICE_CHANNEL_FROM_SERVER_ID", MakeMumbleNative([](fx::ScriptContext& context) { int serverId = context.GetArgument(0); int channelId = -1; - if (g_mumble.connected) - { - auto channelName = g_mumbleClient->GetVoiceChannelFromServerId(serverId); + auto channelName = g_mumbleClient->GetVoiceChannelFromServerId(serverId); - if (!channelName.empty()) + if (!channelName.empty()) + { + if (channelName.find("Game Channel ") == 0) { - if (channelName.find("Game Channel ") == 0) - { - channelId = std::stoi(channelName.substr(13)); - } - else if (channelName == "Root") - { - channelId = 0; - } + channelId = std::stoi(channelName.substr(13)); + } + else if (channelName == "Root") + { + channelId = 0; } } context.SetResult(channelId); - }); + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_IS_CONNECTED", [](fx::ScriptContext& context) + // MakeMumbleNative will return false automatically if we're not connected. + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_IS_CONNECTED", MakeMumbleNative([](fx::ScriptContext& context) { - context.SetResult(g_mumble.connected ? true : false); - }); + context.SetResult(true); + })); fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_SERVER_ADDRESS", [](fx::ScriptContext& context) { auto address = context.GetArgument(0); int port = context.GetArgument(1); - boost::optional overridePeer = net::PeerAddress::FromString(fmt::sprintf("%s:%d", address, port), port); + auto formattedAddress = fmt::sprintf("%s:%d", address, port); + boost::optional overridePeer = net::PeerAddress::FromString(formattedAddress, port); + + // If we set our address to an empty and our port is -1 we should reset our override + if (address == "" && port == -1) + { + g_mumble.overridePeer = {}; + Mumble_Disconnect(true); + return; + } if (overridePeer) { @@ -1266,7 +1323,7 @@ static HookFunction hookFunction([]() } else { - throw std::exception("Couldn't resolve Mumble server address."); + throw std::exception(va("Couldn't resolve Mumble server address %s.", formattedAddress)); } }); @@ -1284,21 +1341,15 @@ static HookFunction hookFunction([]() g_mumbleClient->SetAudioOutputDistance(dist); }); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_CHANNEL", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_CLEAR_VOICE_CHANNEL", MakeMumbleNative([](fx::ScriptContext& context) { - if (g_mumble.connected) - { - g_mumbleClient->SetChannel("Root"); - } - }); + g_mumbleClient->SetChannel("Root"); + })); - fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOICE_CHANNEL", [](fx::ScriptContext& context) + fx::ScriptEngine::RegisterNativeHandler("MUMBLE_SET_VOICE_CHANNEL", MakeMumbleNative([](fx::ScriptContext& context) { - if (g_mumble.connected) - { - g_mumbleClient->SetChannel(fmt::sprintf("Game Channel %d", context.GetArgument(0))); - } - }); + g_mumbleClient->SetChannel(GetMumbleChannel(context.GetArgument(0))); + })); scrBindGlobal("GET_AUDIOCONTEXT_FOR_CLIENT", getAudioContext); scrBindGlobal("GET_AUDIOCONTEXT_FOR_SERVERID", getAudioContextByServerId); @@ -1355,7 +1406,7 @@ static HookFunction hookFunction([]() if (g_mumble.connected) { - g_mumbleClient->SetChannel(fmt::sprintf("Game Channel %d", context.GetArgument(0))); + g_mumbleClient->SetChannel(GetMumbleChannel(context.GetArgument(0))); } }); diff --git a/code/components/voip-mumble/include/MumbleAudioSink.h b/code/components/voip-mumble/include/MumbleAudioSink.h index ad52eb1976..db9b614387 100644 --- a/code/components/voip-mumble/include/MumbleAudioSink.h +++ b/code/components/voip-mumble/include/MumbleAudioSink.h @@ -20,7 +20,7 @@ DLL_IMPORT #else DLL_EXPORT #endif -fwEvent*> +fwEvent*> OnGetMumbleAudioSink; extern diff --git a/code/components/voip-mumble/include/MumbleClient.h b/code/components/voip-mumble/include/MumbleClient.h index f0bbda637c..a2cb92d12f 100644 --- a/code/components/voip-mumble/include/MumbleClient.h +++ b/code/components/voip-mumble/include/MumbleClient.h @@ -49,22 +49,8 @@ enum class MumbleVoiceLikelihood struct VoiceTargetConfig { - struct Target - { - std::vector users; - std::string channel; - // ACL is not supported in umurmur, so does not count - bool links; - bool children; - - inline Target() - : links(false), children(false) - { - - } - }; - - std::list targets; + std::set users; + std::set channels; }; class IMumbleClient : public fwRefCountable @@ -89,11 +75,11 @@ class IMumbleClient : public fwRefCountable virtual void SetChannel(const std::string& channelName) = 0; - virtual void SetClientVolumeOverride(const std::wstring& clientName, float volume) = 0; + virtual void SetClientVolumeOverride(const std::string& clientName, float volume) = 0; virtual void SetClientVolumeOverrideByServerId(uint32_t serverId, float volume) = 0; - virtual std::wstring GetPlayerNameFromServerId(uint32_t serverId) = 0; + virtual std::string GetPlayerNameFromServerId(uint32_t serverId) = 0; virtual std::string GetVoiceChannelFromServerId(uint32_t serverId) = 0; diff --git a/code/components/voip-mumble/include/MumbleClientImpl.h b/code/components/voip-mumble/include/MumbleClientImpl.h index 6d49dfeae7..6784465d5a 100644 --- a/code/components/voip-mumble/include/MumbleClientImpl.h +++ b/code/components/voip-mumble/include/MumbleClientImpl.h @@ -148,11 +148,11 @@ class MumbleClient : public IMumbleClient, public Botan::TLS::Callbacks virtual std::shared_ptr GetAudioContext(const std::string& name) override; - virtual void SetClientVolumeOverride(const std::wstring& clientName, float volume) override; + virtual void SetClientVolumeOverride(const std::string& clientName, float volume) override; virtual void SetClientVolumeOverrideByServerId(uint32_t serverId, float volume) override; - - virtual std::wstring GetPlayerNameFromServerId(uint32_t serverId) override; + + virtual std::string GetPlayerNameFromServerId(uint32_t serverId) override; virtual std::string GetVoiceChannelFromServerId(uint32_t serverId) override; diff --git a/code/components/voip-mumble/include/MumbleClientState.h b/code/components/voip-mumble/include/MumbleClientState.h index 5ab47286b2..18ef665971 100644 --- a/code/components/voip-mumble/include/MumbleClientState.h +++ b/code/components/voip-mumble/include/MumbleClientState.h @@ -19,11 +19,11 @@ class MumbleChannel MumbleClient* m_client; - std::wstring m_channelName; + std::string m_channelName; bool m_hasDescription; - std::wstring m_channelDescription; + std::string m_channelDescription; std::string m_descriptionHash; @@ -32,11 +32,11 @@ class MumbleChannel public: MumbleChannel(MumbleClient* client, MumbleProto::ChannelState& channelState); - inline std::wstring GetName() const { return m_channelName; } + inline std::string GetName() const { return m_channelName; } inline bool HasDescription() const { return m_hasDescription; } - inline std::wstring GetDescription() const { return m_channelDescription; } + inline std::string GetDescription() const { return m_channelDescription; } inline bool IsTemporary() const { return m_temporary; } @@ -52,7 +52,7 @@ class MumbleUser uint32_t m_serverId; - std::wstring m_name; + std::string m_name; uint32_t m_currentChannelId; @@ -78,7 +78,7 @@ class MumbleUser inline uint32_t GetServerId() const { return m_serverId; } - inline std::wstring GetName() const { return m_name; } + inline std::string GetName() const { return m_name; } inline uint32_t GetChannelId() const { return m_currentChannelId; } @@ -92,7 +92,7 @@ class MumbleClientState uint32_t m_session; - std::wstring m_username; + std::string m_username; std::map m_channels; @@ -105,16 +105,16 @@ class MumbleClientState { m_client = nullptr; m_session = 0; - m_username = L""; + m_username = ""; m_channels.clear(); m_users.clear(); } inline void SetClient(MumbleClient* client) { m_client = client; } - inline void SetUsername(std::wstring& value) { m_username = value; } + inline void SetUsername(const std::string& value) { m_username = value; } - inline std::wstring GetUsername() { return m_username; } + inline std::string GetUsername() { return m_username; } inline void SetSession(uint32_t sessionId) { m_session = sessionId; } diff --git a/code/components/voip-mumble/src/MumbleAudioOutput.cpp b/code/components/voip-mumble/src/MumbleAudioOutput.cpp index 090ec5a2d7..39e3e81790 100644 --- a/code/components/voip-mumble/src/MumbleAudioOutput.cpp +++ b/code/components/voip-mumble/src/MumbleAudioOutput.cpp @@ -528,7 +528,7 @@ MumbleAudioOutput::ClientAudioState::~ClientAudioState() } DLL_EXPORT -fwEvent*> +fwEvent*> OnGetMumbleAudioSink; DLL_EXPORT @@ -1508,7 +1508,7 @@ void MumbleAudioOutput::SetAudioDevice(const std::string& deviceId) m_client->GetState().ForAllUsers([this](const std::shared_ptr& user) { - HandleClientConnect(*user); + HandleClientConnect(*user); }); } @@ -1553,8 +1553,6 @@ void DuckingOptOut(WRL::ComPtr device); std::shared_ptr MumbleAudioOutput::GetAudioContext(const std::string& name) { - auto wideName = ToWide(name); - std::shared_lock _(m_clientsMutex); for (auto& client : m_clients) @@ -1566,7 +1564,7 @@ std::shared_ptr MumbleAudioOutput::GetAudioContext(const std: auto user = m_client->GetState().GetUser(client.first); - if (!user || user->GetName() != wideName) + if (!user || user->GetName() != name) { continue; } diff --git a/code/components/voip-mumble/src/MumbleClient.cpp b/code/components/voip-mumble/src/MumbleClient.cpp index d4d5044e5a..fec91d212d 100644 --- a/code/components/voip-mumble/src/MumbleClient.cpp +++ b/code/components/voip-mumble/src/MumbleClient.cpp @@ -24,7 +24,7 @@ static __declspec(thread) MumbleClient* g_currentMumbleClient; using namespace std::chrono_literals; -constexpr auto kUDPPingInterval = 1000ms; +constexpr auto kPingInterval = 1000ms; constexpr uint16_t kMaxUdpPacket = 1024; inline std::chrono::milliseconds msec() @@ -115,6 +115,7 @@ void MumbleClient::Initialize() // don't start idle timer here - it should only start after TLS handshake is done! m_timeSinceJoin = msec(); + m_inFlightTcpPings = 0; m_connectionInfo.isConnected = true; }); @@ -159,13 +160,12 @@ void MumbleClient::Initialize() m_tcp->connect(*address.GetSocketAddress()); m_state.Reset(); m_state.SetClient(this); - m_state.SetUsername(ToWide(m_connectionInfo.username)); + m_state.SetUsername(m_connectionInfo.username); }); m_idleTimer = m_loop->Get()->resource(); m_idleTimer->on([this](const uvw::TimerEvent& ev, uvw::TimerHandle& t) { - auto lockedIsActive = [this]() { std::unique_lock _(m_clientMutex); @@ -178,15 +178,14 @@ void MumbleClient::Initialize() { if (m_curManualChannel != m_lastManualChannel && !m_state.GetChannels().empty()) { - // check if the channel already exists - std::wstring wname = ToWide(m_curManualChannel); m_lastManualChannel = m_curManualChannel; - + bool existed = false; + // check if the channel already exists, if it does set us to the channel for (const auto& channel : m_state.GetChannels()) { - if (channel.second.GetName() == wname) + if (channel.second.GetName() == m_curManualChannel) { // join the channel MumbleProto::UserState state; @@ -224,11 +223,9 @@ void MumbleClient::Initialize() auto findCh = [&](const std::string& ch) { - std::wstring wname = ToWide(ch); - for (const auto& channel : m_state.GetChannels()) { - if (channel.second.GetName() == wname) + if (channel.second.GetName() == ch) { return channel.first; } @@ -287,35 +284,31 @@ void MumbleClient::Initialize() MumbleProto::VoiceTarget target; target.set_id(idx); - for (auto& t : config.targets) + // Voice targets can all be set in a single target + auto vt = target.add_targets(); + for (auto& userName : config.users) { - auto vt = target.add_targets(); - - for (auto& userName : t.users) + m_state.ForAllUsers([this, &userName, &vt](const std::shared_ptr& user) { - m_state.ForAllUsers([this, &userName, &vt](const std::shared_ptr& user) + if (user->GetName() == userName) { - if (user->GetName() == userName) - { - vt->add_session(user->GetSessionId()); - } - }); - } + vt->add_session(user->GetSessionId()); + } + }); + } + - if (!t.channel.empty()) + for (auto& channelName: config.channels) + { + for (auto& channelPair : m_state.GetChannels()) { - std::wstring wname = ToWide(t.channel); - for (auto& channelPair : m_state.GetChannels()) + if (channelPair.second.GetName() == channelName) { - if (channelPair.second.GetName() == wname) - { - vt->set_channel_id(channelPair.first); - } + // Channel targeting happens per channel, so we need to add a new target per channel + auto vt = target.add_targets(); + vt->set_channel_id(channelPair.first); } } - - vt->set_links(t.links); - vt->set_children(t.children); } Send(MumbleMessageType::VoiceTarget, target); @@ -337,7 +330,7 @@ void MumbleClient::Initialize() if (!name.empty()) { - m_lastManualChannel = ToNarrow(name); + m_lastManualChannel = name; } } } @@ -345,10 +338,13 @@ void MumbleClient::Initialize() if (msec() > m_nextPing) { { - // only log once at 4 pings - if (m_inFlightTcpPings == 4) + // reset the connection at 4 pings (which will be about 4 seconds) in case we lose our TCP connection, lazand we haven't just connected + if (m_inFlightTcpPings == 4 && (msec() - m_timeSinceJoin) > 20s) { - console::PrintWarning("mumble", "Server is not responding to TCP pings\n"); + // Reset our connection status so that mumble will try to reconnect us + m_connectionInfo.isConnected = false; + m_connectionInfo.isConnecting = false; + console::PrintWarning("mumble", "Server is not responding to TCP pings after 4 seconds, resetting connection\n"); } m_inFlightTcpPings += 1; @@ -383,7 +379,7 @@ void MumbleClient::Initialize() SendUDP(pingBuf, pds.size()); } - m_nextPing = msec() + kUDPPingInterval; + m_nextPing = msec() + kPingInterval; } } else if (m_connectionInfo.address.GetAddressFamily() != 0) @@ -422,11 +418,10 @@ concurrency::task MumbleClient::ConnectAsync(const net::P m_tcpPingCount = 0; - memset(m_tcpPings, 0, sizeof(m_tcpPings)); m_state.SetClient(this); - m_state.SetUsername(ToWide(userName)); + m_state.SetUsername(userName); m_loop->EnqueueCallback([this]() { @@ -590,7 +585,7 @@ float MumbleClient::GetInputAudioLevel() return m_audioInput.GetAudioLevel(); } -void MumbleClient::SetClientVolumeOverride(const std::wstring& clientName, float volume) +void MumbleClient::SetClientVolumeOverride(const std::string& clientName, float volume) { m_state.ForAllUsers([this, &clientName, volume](const std::shared_ptr& user) { @@ -612,9 +607,9 @@ void MumbleClient::SetClientVolumeOverrideByServerId(uint32_t serverId, float vo }); } -std::wstring MumbleClient::GetPlayerNameFromServerId(uint32_t serverId) +std::string MumbleClient::GetPlayerNameFromServerId(uint32_t serverId) { - std::wstring retName; + std::string retName; m_state.ForAllUsers([serverId, &retName](const std::shared_ptr& user) { @@ -634,10 +629,11 @@ std::wstring MumbleClient::GetPlayerNameFromServerId(uint32_t serverId) std::string MumbleClient::GetVoiceChannelFromServerId(uint32_t serverId) { - std::string retString = ""; + std::string retString; m_state.ForAllUsers([this, serverId, &retString](const std::shared_ptr& user) { + // if we already have a name we can ignore and bail if (!retString.empty()) { return; @@ -651,7 +647,7 @@ std::string MumbleClient::GetVoiceChannelFromServerId(uint32_t serverId) if (chit != channels.end()) { - retString = ToNarrow(chit->second.GetName()); + retString = chit->second.GetName(); } } }); @@ -661,11 +657,9 @@ std::string MumbleClient::GetVoiceChannelFromServerId(uint32_t serverId) bool MumbleClient::DoesChannelExist(const std::string& channelName) { - std::wstring wname = ToWide(channelName); - for (const auto& channel : m_state.GetChannels()) { - if (channel.second.GetName() == wname) + if (channel.second.GetName() == channelName) { return true; } @@ -687,14 +681,14 @@ void MumbleClient::GetTalkers(std::vector* referenceIds) if (user) { - referenceIds->push_back(ToNarrow(user->GetName())); + referenceIds->push_back(user->GetName()); } } // local talker talking? if (m_audioInput.IsTalking()) { - referenceIds->push_back(ToNarrow(m_state.GetUsername())); + referenceIds->push_back(m_state.GetUsername()); } } @@ -775,7 +769,6 @@ void MumbleClient::HandleUDP(const uint8_t* buf, size_t size) return; } - // handle voice packet HandleVoice(outBuf, size - 4); } @@ -928,7 +921,7 @@ void MumbleClient::RunFrame() { if (m_positionHook) { - auto newPos = m_positionHook(ToNarrow(user->GetName())); + auto newPos = m_positionHook(user->GetName()); if (newPos) { diff --git a/code/components/voip-mumble/src/MumbleClientState_Channel.cpp b/code/components/voip-mumble/src/MumbleClientState_Channel.cpp index 7bd4010baa..4f7b97c044 100644 --- a/code/components/voip-mumble/src/MumbleClientState_Channel.cpp +++ b/code/components/voip-mumble/src/MumbleClientState_Channel.cpp @@ -30,12 +30,12 @@ void MumbleChannel::UpdateChannel(MumbleProto::ChannelState& state) if (state.has_name()) { - m_channelName = ConvertFromUTF8(state.name()); + m_channelName = state.name(); } if (state.has_description()) { - m_channelDescription = ConvertFromUTF8(state.description()); + m_channelDescription = state.description(); m_hasDescription = true; } diff --git a/code/components/voip-mumble/src/MumbleClientState_User.cpp b/code/components/voip-mumble/src/MumbleClientState_User.cpp index 2d5cf091ba..a02454f324 100644 --- a/code/components/voip-mumble/src/MumbleClientState_User.cpp +++ b/code/components/voip-mumble/src/MumbleClientState_User.cpp @@ -35,11 +35,10 @@ void MumbleUser::UpdateUser(MumbleProto::UserState& state) if (state.has_name()) { - std::string name = state.name(); - m_name = ConvertFromUTF8(name); - if (name.length() >= 2) + m_name = state.name(); + if (m_name.length() >= 2) { - m_serverId = atoi(name.substr(1, name.length() - 1).c_str()); + m_serverId = atoi(m_name.substr(1, m_name.length() - 1).c_str()); } else { @@ -77,7 +76,7 @@ void MumbleUser::UpdateUser(MumbleProto::UserState& state) m_currentChannelId = state.channel_id(); } - console::DPrintf("mumble", "%s joined channel %d\n", ConvertToUTF8(m_name), m_currentChannelId); + console::DPrintf("mumble", "%s joined channel %d\n", m_name, m_currentChannelId); } void MumbleClientState::ProcessUserState(MumbleProto::UserState& userState) @@ -100,7 +99,7 @@ void MumbleClientState::ProcessUserState(MumbleProto::UserState& userState) createdUser = user; - console::DPrintf("Mumble", "New user: %s\n", ToNarrow(user->GetName())); + console::DPrintf("Mumble", "New user: %s\n", user->GetName()); } else { diff --git a/code/components/voip-mumble/src/MumbleVersionHandler.cpp b/code/components/voip-mumble/src/MumbleVersionHandler.cpp index f73341b07f..6e75600ad7 100644 --- a/code/components/voip-mumble/src/MumbleVersionHandler.cpp +++ b/code/components/voip-mumble/src/MumbleVersionHandler.cpp @@ -15,11 +15,10 @@ DEFINE_HANDLER(Version) // also send our initial registration packet auto username = client->GetState().GetUsername(); - auto usernameUtf8 = ConvertToUTF8(username); MumbleProto::Authenticate authenticate; authenticate.set_opus(true); - authenticate.set_username(usernameUtf8); + authenticate.set_username(username); client->Send(MumbleMessageType::Authenticate, authenticate); diff --git a/ext/native-decls/MumbleSetServerAddress.md b/ext/native-decls/MumbleSetServerAddress.md index 1f96c2af97..7e2488c54b 100644 --- a/ext/native-decls/MumbleSetServerAddress.md +++ b/ext/native-decls/MumbleSetServerAddress.md @@ -10,6 +10,8 @@ void MUMBLE_SET_SERVER_ADDRESS(char* address, int port); Changes the Mumble server address to connect to, and reconnects to the new address. +Setting the address to an empty string and the port to 0 will reset to the built in FXServer Mumble Implementation. + ## Parameters * **address**: The address of the mumble server. * **port**: The port of the mumble server.