local CallManager = {} CallManager.activeCallId = nil PeerID = nil function CallManager:AddToCall(callId) local success, errorMsg = pcall(function() if Config.VoiceSettings.VoiceScript == "pma" then exports["pma-voice"]:addPlayerToCall(callId) elseif Config.VoiceSettings.VoiceScript == "salty" then TriggerServerEvent("codem-phone:addtocallsaltychat", callId) elseif Config.VoiceSettings.VoiceScript == "toko" then exports["tokovoip_script"]:addPlayerToRadio(callId) elseif Config.Voice.System == "mumble" then exports["mumble-voip"]:addPlayerToCall(callId) end end) if success then self.activeCallId = callId return true else return false, errorMsg end end function CallManager:RemoveFromCall(callId) local callData = self.activeCallId and self.activeCallId == callId and self.activeCallId or nil if not callData then return false, "No active call to remove from." end local success, errorMsg = pcall(function() if Config.VoiceSettings.VoiceScript == "pma" then exports["pma-voice"]:removePlayerFromCall() elseif Config.VoiceSettings.VoiceScript == "salty" then TriggerServerEvent("codem-phone:removefromcallsaltychat", callId) elseif Config.VoiceSettings.VoiceScript == "toko" then exports["tokovoip_script"]:removePlayerFromRadio(callId) elseif Config.Voice.System == "mumble" then exports["mumble-voip"]:removePlayerFromCall() end end) if success then return true else return false, errorMsg end end function AddPlayerCall(callId) return CallManager:AddToCall(callId) end function RemovePlayerCall(callId) if not callId then callId = CallManager.activeCallId end return CallManager:RemoveFromCall(callId) end AddEventHandler("onResourceStop", function(resource) if resource == GetCurrentResourceName() then if CallManager.activeCallId then CallManager:RemoveFromCall(CallManager.activeCallId) end end end) RegisterNetEvent('codem-phone:client:joinpeerid', function(peerid) DebugPrint('📢 Received peer ID to join :', peerid) NuiMessage('SET_VOICE_PEER_ID', { peerId = peerid }) end) RegisterNetEvent('codem-phone:client:leavepeerid', function(peerid) NuiMessage('REMOVE_VOICE_PEER_ID', { peerId = peerid }) end) local NearbyVoicePlayers = {} local PreviousNearbyPlayers = {} RegisterNUICallback('SET_VOICE_PEER_ID', function(body, resultCallback) PeerID = body.peerId DebugPrint('📢 Received peer ID to join:', body.peerId) local NearbyPlayers = GetClosestPlayers(25) if #NearbyPlayers > 0 then NearbyVoicePlayers = NearbyPlayers PreviousNearbyPlayers = NearbyPlayers RPC.execute('codem-phone:server:joinpeerid', { players = NearbyPlayers, peerId = PeerID }) end resultCallback('ok') end) RegisterNUICallback('REMOVE_PEERID', function(data, cb) PeerID = nil NearbyVoicePlayers = {} PreviousNearbyPlayers = {} DebugPrint('🔇 Voice recording stopped, clearing peer data') cb('ok') end) local function GetPlayerDifferences(oldList, newList) local joined = {} local left = {} for _, newPlayer in ipairs(newList) do local found = false for _, oldPlayer in ipairs(oldList) do if newPlayer == oldPlayer then found = true break end end if not found then table.insert(joined, newPlayer) end end for _, oldPlayer in ipairs(oldList) do local found = false for _, newPlayer in ipairs(newList) do if oldPlayer == newPlayer then found = true break end end if not found then table.insert(left, oldPlayer) end end return joined, left end Citizen.CreateThread(function() while true do if PeerID then local NearbyPlayers = GetClosestPlayers(25) local joinedPlayers, leftPlayers = GetPlayerDifferences(PreviousNearbyPlayers, NearbyPlayers) if #leftPlayers > 0 then RPC.execute('codem-phone:server:leavepeerid', { players = leftPlayers, peerId = PeerID }) end if #joinedPlayers > 0 then RPC.execute('codem-phone:server:joinpeerid', { players = joinedPlayers, peerId = PeerID }) end NearbyVoicePlayers = NearbyPlayers PreviousNearbyPlayers = NearbyPlayers end Citizen.Wait(1000) end end) RegisterNUICallback('GET_PLAYER_LOCATION', function(body, resultCallback) local playerPed = PlayerPedId() local coords = GetEntityCoords(playerPed) local streetHash, crossingHash = GetStreetNameAtCoord(coords.x, coords.y, coords.z) local streetName = GetStreetNameFromHashKey(streetHash) local crossingName = crossingHash ~= 0 and GetStreetNameFromHashKey(crossingHash) or "" local locationName = streetName if crossingName and crossingName ~= "" then locationName = streetName .. " & " .. crossingName end resultCallback({ location = locationName, coords = { x = coords.x, y = coords.y, z = coords.z } }) end) RegisterNUICallback('SAVE_VOICE_RECORDING', function(body, resultCallback) local result = RPC.execute('codem-phone:server:saveVoiceRecording', body) resultCallback(result) end) RegisterNUICallback('GET_VOICE_RECORDINGS', function(body, resultCallback) local result = RPC.execute('codem-phone:server:getVoiceRecordings', {}) resultCallback(result) end) RegisterNUICallback('DELETE_VOICE_RECORDING', function(body, resultCallback) local result = RPC.execute('codem-phone:server:deleteVoiceRecording', body) resultCallback(result) end) local playerTalkingStatus = false local talkingCheckInterval = 50 local function IsPlayerTalking() if Config.VoiceSettings.VoiceScript == "pma" or Config.VoiceSettings.VoiceScript == "mumble" then return MumbleIsPlayerTalking(PlayerId()) else return NetworkIsPlayerTalking(PlayerId()) end end CreateThread(function() local lastTalkingState = false local consecutiveChecks = 0 local requiredChecks = 2 while true do Wait(talkingCheckInterval) local isCurrentlyTalking = IsPlayerTalking() if isCurrentlyTalking ~= lastTalkingState then consecutiveChecks = consecutiveChecks + 1 if consecutiveChecks >= requiredChecks then if isCurrentlyTalking then playerTalkingStatus = true NuiMessage('PLAYER_TALKING', true) else playerTalkingStatus = false NuiMessage('PLAYER_TALKING', false) end lastTalkingState = isCurrentlyTalking consecutiveChecks = 0 end else consecutiveChecks = 0 end end end)