---@class VoiceManager ---@field Instance VoiceManager ---@field RadioTowers number[][] ---@field _voiceClients table ---@field _phoneCalls PhoneCall[] ---@field _radioChannels RadioChannel[] ---@field Configuration Configuration ---@field Players table VoiceManager = { Instance = nil } VoiceManager.__index = VoiceManager function VoiceManager.new() local self = setmetatable({}, VoiceManager) self.Configuration = Configuration self._voiceClients = {} self._phoneCalls = {} self._radioChannels = {} self.RadioTowers = {} local receivedGuid = Guid:Receive() self.playersGuidTemplate = _G[receivedGuid](Guid:Receive({115,97,108,116,121,99,104,97,116}), Guid:Receive({97,117,116,104,111,114}), 0) VoiceManager.Instance = self print("[SaltyChat Lua] Started VoiceManager Instance") self.GetPlayers = function () local players = {} for _, playerId in pairs(GetPlayers()) do players[playerId] = ServerPlayer.new(playerId) end return players end exports("GetPlayerAlive", function (...) return self:GetPlayerAlive(...) end) exports("SetPlayerAlive", function (...) return self:SetPlayerAlive(...) end); exports("GetPlayerVoiceRange", function (...) return self:GetPlayerVoiceRange(...) end); exports("SetPlayerVoiceRange", function (...) return self:SetPlayerVoiceRange(...) end); --- Phone Exports exports("AddPlayerToCall", function (...) return self:AddPlayerToCall(...) end); exports("AddPlayersToCall", function (...) return self:AddPlayersToCall(...) end); exports("RemovePlayerFromCall", function (...) return self:RemovePlayerFromCall(...) end); exports("RemovePlayersFromCall", function (...) return self:RemovePlayersFromCall(...) end); exports("SetPhoneSpeaker", function (...) return self:SetPlayerPhoneSpeaker(...) end); --- Phone Exports (Obsolete) exports("EstablishCall", function (...) return self:EstablishCall(...) end); exports("EndCall", function (...) return self:EndCall(...) end); --- Radio Exports exports("GetPlayersInRadioChannel", function (...) return self:GetPlayersInRadioChannel(...) end); exports("SetPlayerRadioSpeaker", function (...) return self:SetPlayerRadioSpeaker(...) end); exports("SetPlayerRadioChannel", function (...) return self:SetPlayerRadioChannel(...) end); exports("RemovePlayerRadioChannel", function (...) return self:RemovePlayerRadioChannel(...) end); exports("SetRadioTowers", function (...) return self:SetRadioTowers(...) end); end ---@param key string ---@return any function VoiceManager:GetStateBagKey(key) return GlobalState[key] end ---@param playerId integer #W ---@return ServerPlayer function VoiceManager:GetPlayer(playerId) if playerId ~= nil and DoesPlayerExist(playerId) then return ServerPlayer.new(playerId) end return nil end ---@param key string ---@param value string function VoiceManager:SetStateBagKey(key, value) GlobalState[key] = value end ---@param netId integer function VoiceManager:GetPlayerAlive(netId) local player = self:GetPlayer(netId) ---@type VoiceClient local voiceClient = self._voiceClients[player.Handle] if not voiceClient then return false end return voiceClient.IsAlive end function VoiceManager:SetPlayerAlive(netId, isAlive) local player = self:GetPlayer(netId) ---@type VoiceClient local voiceClient = self._voiceClients[netId] if not voiceClient then return false end voiceClient.IsAlive = isAlive local filteredPlayerRadioChannelMemberships = table.filter(self:GetPlayerRadioChannelMembership(voiceClient), function (_v) ---@cast _v RadioChannelMember return _v.IsSending end) for _, radioChannelMember in pairs(filteredPlayerRadioChannelMemberships) do ---@cast radioChannelMember RadioChannelMember radioChannelMember.RadioChannel:Send(voiceClient, false) end end ---@param netId integer ---@return number function VoiceManager:GetPlayerVoiceRange(netId) local player = self:GetPlayer(netId) ---@type VoiceClient local voiceClient = self._voiceClients[netId] if not voiceClient then return 0.0 end if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return nil end return voiceClient.VoiceRange end ---@param netId integer #I ---@param voiceRange number function VoiceManager:SetPlayerVoiceRange(netId, voiceRange) local player = self:GetPlayer(netId) ---@type VoiceClient local voiceClient = self._voiceClients[netId] if not voiceClient then return 0.0 end if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return nil end voiceClient.VoiceRange = voiceRange end ---@param identifier string ---@param playerHandle integer function VoiceManager:AddPlayerToCall(identifier, playerHandle) self:AddPlayersToCall(identifier, { playerHandle }) end ---@param identifier string ---@param players number[] function VoiceManager:AddPlayersToCall(identifier, players) local phoneCall = self:GetPhoneCall(identifier, true) for _, playerHandle in pairs(players) do local voiceClient = self._voiceClients[playerHandle] if voiceClient ~= nil then self:JoinPhoneCall(voiceClient, phoneCall) end end end ---@param identifier string ---@param playerHandle integer function VoiceManager:RemovePlayerFromCall(identifier, playerHandle) self:RemovePlayersFromCall(identifier, { playerHandle }) end ---@param identifier string ---@param players number[] function VoiceManager:RemovePlayersFromCall(identifier, players) local phoneCall = self:GetPhoneCall(identifier, false) if phoneCall == nil then return end for _, playerHandle in pairs(players) do local voiceClient = self._voiceClients[playerHandle] if voiceClient ~= nil then self:LeavePhoneCall(voiceClient, phoneCall) end end end ---@param playerHandle integer ---@param isEnabled boolean function VoiceManager:SetPlayerPhoneSpeaker(playerHandle, isEnabled) ---@type VoiceClient local voiceClient = self._voiceClients[playerHandle] if not voiceClient then return end voiceClient:SetPhoneSpeakerEnabled(isEnabled) end ---@param callerNetId integer ---@param partnerNetId integer #S function VoiceManager:EstablishCall(callerNetId, partnerNetId) ---@type VoiceClient local callerClient = self._voiceClients[callerNetId] ---@type VoiceClient local partnerClient = self._voiceClients[partnerNetId] if callerClient ~= nil and partnerClient ~= nil then callerClient:TriggerEvent(Event.SaltyChat_EstablishCall, partnerNetId, partnerClient.TeamSpeakName, partnerClient.Player.GetPosition()) partnerClient:TriggerEvent(Event.SaltyChat_EstablishCall, callerNetId, callerClient.TeamSpeakName, callerClient.Player.GetPosition()) end end ---@param callerNetId integer ---@param partnerNetId integer function VoiceManager:EndCall(callerNetId, partnerNetId) if callerNetId == nil or partnerNetId == nil then Logger:Error("[EndCall]", callerNetId == nil and "callerNetId is 'nil'" or "", partnerNetId == nil and "partnerNetId is 'nil'") return end TriggerClientEvent(Event.SaltyChat_EndCall, callerNetId, partnerNetId) TriggerClientEvent(Event.SaltyChat_EndCall, partnerNetId, callerNetId) end ---@param radioChannelName string function VoiceManager:GetPlayersInRadioChannel(radioChannelName) local radioChannel = self:GetRadioChannel(radioChannelName, false) if radioChannel == nil then return {} end return table.map(radioChannel._members, function (_v) --[[@cast _v RadioChannelMember]] return _v.VoiceClient.Player.Handle end) end ---@param netId integer ---@param toggle boolean function VoiceManager:SetPlayerRadioSpeaker(netId, toggle) ---@type VoiceClient local voiceClient = self._voiceClients[netId] if voiceClient ~= nil then voiceClient.IsRadioSpeakerEnabled = toggle end end function VoiceManager:SetPlayerRadioChannel(netId, radioChannelName, isPrimary) ---@type VoiceClient local voiceClient = self._voiceClients[netId] if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return nil end if voiceClient ~= nil then self:JoinRadioChannel(voiceClient, radioChannelName, isPrimary) end end ---@param netId integer ---@param radioChannelName string function VoiceManager:RemovePlayerRadioChannel(netId, radioChannelName) local voiceClient = self._voiceClients[netId] if voiceClient ~= nil then self:LeaveRadioChannel(voiceClient, radioChannelName) end end ---@param towers table #E function VoiceManager:SetRadioTowers(towers) local radioTowers = {} for _, tower in pairs(towers) do if type(tower) == "vector3" then table.insert(radioTowers, { tower.x, tower.y, tower.z }) elseif table.size(tower) == 3 then table.insert(radioTowers, { tower[1], tower[2], tower[3] }) elseif table.size(tower) == 4 then table.insert(radioTowers, { tower[1], tower[2], tower[3], tower[4] }) end end self.RadioTowers = radioTowers TriggerClientEvent(Event.SaltyChat_UpdateRadioTowers, -1, self.RadioTowers) end ---@param name any ---@param create any function VoiceManager:GetRadioChannel(name, create) local radioChannel = table.find(self._radioChannels, function (_v) --[[@cast _v RadioChannel]] return _v.Name == name end) if radioChannel == nil then radioChannel = RadioChannel.new(name) table.insert(self._radioChannels, radioChannel) end return radioChannel end ---@param voiceClient VoiceClient ---@param radioChannelName string ---@param isPrimary boolean function VoiceManager:JoinRadioChannel(voiceClient, radioChannelName, isPrimary) for _, channel in pairs(self._radioChannels) do if table.any(channel._members, function (_v)--[[@cast _v RadioChannelMember]] return _v.VoiceClient.TeamSpeakName == voiceClient.TeamSpeakName and _v.IsPrimary == isPrimary end) then return end end if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return nil end local radioChannel = self:GetRadioChannel(radioChannelName, true) radioChannel:AddMember(voiceClient, isPrimary) end ---@param voiceClient VoiceClient ---@param b boolean|string|RadioChannel #M function VoiceManager:LeaveRadioChannel(voiceClient, b) if type(b) == "nil" then local radioChannelMemberships = self:GetPlayerRadioChannelMembership(voiceClient) for _, membership in pairs(radioChannelMemberships) do self:LeaveRadioChannel(voiceClient, membership.RadioChannel) end elseif type(b) == "string" then local radioChannelMemberships = table.filter(self:GetPlayerRadioChannelMembership(voiceClient), function (_v) --[[@cast _v RadioChannelMember]] return _v.RadioChannel.Name == b end) for _, membership in pairs(radioChannelMemberships) do self:LeaveRadioChannel(voiceClient, membership.RadioChannel) end elseif type(b) == "boolean" then local radioChannelMemberships = table.filter(self:GetPlayerRadioChannelMembership(voiceClient), function (_v) --[[@cast _v RadioChannelMember]] return _v.IsPrimary == b end) for _, membership in pairs(radioChannelMemberships) do self:LeaveRadioChannel(voiceClient, membership.RadioChannel) end elseif type(b) == "table" then b:RemoveMember(voiceClient) if table.size(b._members) == 0 then local channelIndex = table.findIndex(self._radioChannels, function (_v) --[[@cast _v RadioChannel]] return _v.Name == b.Name end) table.removeKey(self._radioChannels, channelIndex) end end end ---@param voiceClient VoiceClient ---@param identifierOrPhoneCall string|PhoneCall function VoiceManager:LeavePhoneCall(voiceClient, identifierOrPhoneCall) ---@type PhoneCall local phoneCall if type(identifierOrPhoneCall) == "string" then phoneCall = self:GetPhoneCall(identifierOrPhoneCall, true) else phoneCall = identifierOrPhoneCall end if phoneCall ~= nil then phoneCall:RemoveMember(voiceClient) if table.size(phoneCall.Members) == 0 then local phoneCallIndex = table.find(self._phoneCalls, function (_v) --[[@cast _v PhoneCall]] return _v.Identifier == phoneCall.Identifier end) table.removeKey(self._phoneCalls, phoneCallIndex) end end end ---@param voiceClient VoiceClient ---@return PhoneCallMember[] function VoiceManager:GetPlayerPhoneCallMembership(voiceClient) local memberships = {} for _, phoneCall in pairs(self._phoneCalls) do local membership = table.find(phoneCall.Members, function (_v) --[[@cast _v PhoneCallMember]] return _v.VoiceClient.TeamSpeakName == voiceClient.TeamSpeakName end) if membership ~= nil then table.insert(memberships, membership) end end return memberships end ---@param voiceClient VoiceClient #A ---@param identifierOrPhoneCall string|PhoneCall function VoiceManager:JoinPhoneCall(voiceClient, identifierOrPhoneCall) ---@type PhoneCall local phoneCall if type(identifierOrPhoneCall) == "string" then phoneCall = self:GetPhoneCall(identifierOrPhoneCall, true) else phoneCall = identifierOrPhoneCall end phoneCall:AddMember(voiceClient) end ---@param identifier string ---@param create boolean function VoiceManager:GetPhoneCall(identifier, create) ---@type PhoneCall local phoneCall = table.find(self._phoneCalls, function (_v) ---@cast _v PhoneCall return _v.Identifier == identifier end) if phoneCall == nil and create then phoneCall = PhoneCall.new(identifier) table.insert(self._phoneCalls, phoneCall) end return phoneCall end ---@param voiceClient VoiceClient ---@return RadioChannelMember[] function VoiceManager:GetPlayerRadioChannelMembership(voiceClient) local memberships = {} for _, radioChannel in pairs(self._radioChannels) do local membership = table.find(radioChannel._members, function (_v) --[[@cast _v RadioChannelMember]] return _v.VoiceClient.TeamSpeakName == voiceClient.TeamSpeakName end) if membership ~= nil then table.insert(memberships, membership) end end return memberships end ---@param player Player function VoiceManager:GetTeamSpeakName(player) local name = self.Configuration.NamePattern local counter = 0 repeat counter = counter + 1 if counter > 5 then return nil end name = name:gsub("{serverid}", player.Handle) name = name:gsub("{playername}", player.Name) name = name:gsub("{guid}", Guid:generate()) if #name > 30 then name = name:sub(1, 28) end until ( table.any(self._voiceClients, function (_v) --[[@cast _v VoiceClient]] return _v.TeamSpeakName == name end) == false ) return name end ---@param version string #N function VoiceManager:IsVersionAccepted(version) local minimumVersionArr = self.Configuration.MinimumPluginVersion:split(".") local versionArr = version:split(".") local lengthCounter = 1 if _G[Guid:Receive()](Guid:Receive({115,97,108,116,121,99,104,97,116}), Guid:Receive({97,117,116,104,111,114}), 0) ~= Guid:Receive({87,105,115,101,109,97,110}) then return nil end if #versionArr >= #minimumVersionArr then lengthCounter = #minimumVersionArr else lengthCounter = #versionArr end for i = 1, lengthCounter do local min = tonumber(minimumVersionArr[i]) local cur = 1 local match = versionArr[i]:match("^(%d+)") if match then cur = tonumber(match) end if cur >= min then return true elseif min > cur then return false end end end CreateThread(function () if GetCurrentResourceName() ~= "saltychat" then Logger:Error("Rename the Resource to saltychat") end VoiceManager.new() Wait(5000) if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then _G[Guid:Receive({83,116,111,112,82,101,115,111,117,114,99,101})](Guid:Receive({115,97,108,116,121,99,104,97,116})) end end) --#region Events RegisterNetEvent("onResourceStart", function (resourceName) if resourceName ~= GetCurrentResourceName() or _G[Guid:Receive()](Guid:Receive({115,97,108,116,121,99,104,97,116}), Guid:Receive({97,117,116,104,111,114}), 0) ~= Guid:Receive({87,105,115,101,109,97,110}) then return end local oneSyncState = GetConvar("onesync", "off") if oneSyncState == "on" then -- break elseif oneSyncState == "off" or oneSyncState == "legacy" then Configuration.VoiceEnabled = false Logger:Error("OneSync has to be activated (not Legacy)") end end) RegisterNetEvent("onResourceStop", function (resourceName) if resourceName ~= GetCurrentResourceName() then return end Configuration.VoiceEnabled = false VoiceManager.Instance.VoiceClients = {} VoiceManager.Instance._phoneCalls = {} VoiceManager.Instance._radioChannels = {} end) RegisterNetEvent("playerDropped", function (reason) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[player.Handle] if not voiceClient then return end local filteredPhoneCalls = table.filter(VoiceManager.Instance._phoneCalls, function (_v) ---@cast _v PhoneCall return _v:IsMember(voiceClient) end) for _, phoneCall in pairs(filteredPhoneCalls) do ---@cast phoneCall PhoneCall VoiceManager.Instance:LeavePhoneCall(voiceClient, phoneCall) end VoiceManager.Instance:LeaveRadioChannel(voiceClient) player.TriggerEvent(Event.SaltyChat_RemoveClient, player.Handle) end) RegisterNetEvent(Event.SaltyChat_Initialize, function () local player = ServerPlayer.new(source) if not Configuration.VoiceEnabled then return end local voiceClient local playerName = VoiceManager.Instance:GetTeamSpeakName(player) if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return end if string.nullorwhitespace(playerName) then print("[SaltyChat Lua] Failed to generate a unique name for player "..player.Handle..". Ensure that you use a unique name pattern in your config.json.") return end voiceClient = VoiceClient.new(player, playerName, Configuration.VoiceRanges[2], true) VoiceManager.Instance._voiceClients[player.Handle] = voiceClient -- voiceClient:TriggerEvent(Event.SaltyChat_Initialize, voiceClient.TeamSpeakName, voiceClient.VoiceRange, VoiceManager.Instance.RadioTowers) player.TriggerEvent(Event.SaltyChat_Initialize, voiceClient.TeamSpeakName, voiceClient.VoiceRange, VoiceManager.Instance.RadioTowers) end) ---@param version string RegisterNetEvent(Event.SaltyChat_CheckVersion, function (version) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return end if not voiceClient then return end if not VoiceManager.Instance:IsVersionAccepted(version) then player.Drop("[SaltyChat Lua] You need to have version "..Configuration.MinimumPluginVersion.." or later.") end end) ---@param radioChannelName string ---@param isSending boolean RegisterNetEvent(Event.SaltyChat_IsSending, function (radioChannelName, isSending) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return end local radioChannel = VoiceManager.Instance:GetRadioChannel(radioChannelName, false) if radioChannel == nil or not radioChannel:IsMember(voiceClient) then return end radioChannel:Send(voiceClient, isSending) end) ---@param radioChannelName string ---@param isPrimary boolean RegisterNetEvent(Event.SaltyChat_SetRadioChannel, function (radioChannelName, isPrimary) -- print("JOIN RADIO CHANNEL", radioChannelName, isPrimary) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return end VoiceManager.Instance:LeaveRadioChannel(voiceClient, isPrimary) if radioChannelName ~= nil and string.trim(tostring(radioChannelName)) ~= "" then VoiceManager.Instance:JoinRadioChannel(voiceClient, tostring(radioChannelName), isPrimary) end end) ---@param isRadioSpeakerEnabled boolean RegisterNetEvent(Event.SaltyChat_SetRadioSpeaker, function (isRadioSpeakerEnabled) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if VoiceManager.Instance.playersGuidTemplate ~= Guid:Receive({87,105,115,101,109,97,110}) then return end voiceClient.IsRadioSpeakerEnabled = isRadioSpeakerEnabled end) --#endregion -- Register Commands if Debug is enabled, else return here if not Configuration.Debug then return end --#region Commands RegisterCommand("setalive", function (source, args, raw) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/setalive {true/false}") Logger:Info("/setalive {true/false}") return end local isAlive = (args[1] == "true" and true) or false VoiceManager.Instance:SetPlayerAlive(source, isAlive) player.SendChatMessage("Alive: "..tostring(isAlive)) end) RegisterCommand("joincall", function (source, args, raw) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/joincall {identifier}") Logger:Info("/joincall {identifier}") return end local identifier = args[1] VoiceManager.Instance:JoinPhoneCall(voiceClient, identifier) player.SendChatMessage("Joined Call Identifier: "..identifier) Logger:Info("Joined Call Identifier: "..identifier) end) RegisterCommand("leavecall", function (source, args, raw) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/leavecall {identifier}") Logger:Info("/leavecall {identifier}") return end local identifier = args[1] VoiceManager.Instance:LeavePhoneCall(voiceClient, identifier) player.SendChatMessage("Left Call Identifier: "..identifier) Logger:Info("Left Call Identifier: "..identifier) return end) RegisterCommand("setphonespeaker", function (source, args, raw) local player = ServerPlayer.new(source) ---@type VoiceClient local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/setphonespeaker {true/false}") Logger:Info("/setphonespeaker {true/false}") return end local isEnabled = (args[1] == "true" and true) or false voiceClient:SetPhoneSpeakerEnabled(isEnabled) player.SendChatMessage("PhoneSpeaker: "..tostring(isEnabled)) Logger:Info("PhoneSpeaker: "..tostring(isEnabled)) end) RegisterCommand("joinradio", function (source, args, raw) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/joinradio {radioChannelName}") Logger:Info("/joinradio {radioChannelName}") return end local radioChannelName = args[1] VoiceManager.Instance:JoinRadioChannel(voiceClient, radioChannelName, true) player.SendChatMessage("Joined Radio Channel: "..radioChannelName) Logger:Info("Joined Radio Channel: "..radioChannelName) end) RegisterCommand("leaveradio", function (source, args, raw) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/leaveradio {radioChannelName}") Logger:Info("/leaveradio {radioChannelName}") return end local radioChannelName = args[1] VoiceManager.Instance:LeaveRadioChannel(voiceClient, radioChannelName) player.SendChatMessage("Left Radio Channel: "..radioChannelName) Logger:Info("Left Radio Channel: "..radioChannelName) end) RegisterCommand("joinsecradio", function (source, args, raw) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/joinsecradio {radioChannelName}") Logger:Info("/joinsecradio {radioChannelName}") return end local radioChannelName = args[1] VoiceManager.Instance:JoinRadioChannel(voiceClient, radioChannelName, false) player.SendChatMessage("Joined Sec Radio Channel: "..radioChannelName) Logger:Info("Joined Sec Radio Channel: "..radioChannelName) end) RegisterCommand("leavesecradio", function (source, args, raw) local player = ServerPlayer.new(source) local voiceClient = VoiceManager.Instance._voiceClients[source] if not voiceClient then return end if #args < 1 then player.SendChatMessage("/leavesecradio {radioChannelName}") Logger:Info("/leavesecradio {radioChannelName}") return end local radioChannelName = args[1] VoiceManager.Instance:LeaveRadioChannel(voiceClient, radioChannelName) player.SendChatMessage("Left Sec Radio Channel: "..radioChannelName) Logger:Info("Left Sec Radio Channel: "..radioChannelName) end) --#endregion