diff --git a/fivem_script/tokovoip_script/c_config.lua b/fivem_script/tokovoip_script/c_config.lua index 629e8112..5891213b 100644 --- a/fivem_script/tokovoip_script/c_config.lua +++ b/fivem_script/tokovoip_script/c_config.lua @@ -16,6 +16,8 @@ TokoVoipConfig = { keySwitchChannelsSecondary = Keys["LEFTSHIFT"], -- If set, both the keySwitchChannels and keySwitchChannelsSecondary keybinds must be pressed to switch the radio channels keyProximity = Keys["Z"], -- Keybind used to switch the proximity mode radioClickMaxChannel = 100, -- Set the max amount of radio channels that will have local radio clicks enabled + enableDispatch = false, -- change to true to enable support for the dispatch client + dispatchRadioMaxChannel = 10, -- Set the max channel for the dispatch client, only matters if enableDispatch is set to true radioAnim = true, -- Enable or disable the radio animation radioEnabled = true, -- Enable or disable using the radio wsServer = "ip:port", -- Address of the websocket server diff --git a/fivem_script/tokovoip_script/nui/script.js b/fivem_script/tokovoip_script/nui/script.js index dabd173a..63dcd723 100644 --- a/fivem_script/tokovoip_script/nui/script.js +++ b/fivem_script/tokovoip_script/nui/script.js @@ -201,6 +201,12 @@ function receivedClientCall (event) { } else if (eventName == 'toggleLatency') { displayLatency = !displayLatency; document.querySelector('#latency').style.display = (displayLatency) ? 'block' : 'none'; + } else if (eventName == 'lChannel') { + leaveChannel(payload); + } else if (eventName == 'jChannel') { + joinChannel(payload); + } else if (eventName == 'talkStatusDS') { + websocket.send(`42${JSON.stringify(['talkingStatus', { status: payload }])}`); } } @@ -221,6 +227,14 @@ function receivedClientCall (event) { updateTokovoipInfo(); } +function leaveChannel (data) { + websocket.send(`42${JSON.stringify(['channels', { type: 1, channel: data.channel, uuid: data.uuid }])}`); +} + +function joinChannel (data) { + websocket.send(`42${JSON.stringify(['channels', { type: 0, channel: data.channel, uuid: data.uuid }])}`); +} + function checkPluginStatus () { switch (parseInt(voip.pluginStatus)) { case -1: diff --git a/fivem_script/tokovoip_script/src/c_TokoVoip.lua b/fivem_script/tokovoip_script/src/c_TokoVoip.lua index 1ad155f6..5b280e7b 100644 --- a/fivem_script/tokovoip_script/src/c_TokoVoip.lua +++ b/fivem_script/tokovoip_script/src/c_TokoVoip.lua @@ -18,6 +18,20 @@ TokoVoip = {}; TokoVoip.__index = TokoVoip; local lastTalkState = false +function TokoVoip.leaveChannel(self, lChannel, lUuid) + local dat = {}; + dat.channel = lChannel; + dat.uuid = lUuid; + self:updatePlugin("lChannel", dat); +end + +function TokoVoip.joinChannel(self, lChannel, lUuid) + local dat = {}; + dat.channel = lChannel; + dat.uuid = lUuid; + self:updatePlugin("jChannel", dat); +end + function TokoVoip.init(self, config) local self = setmetatable(config, TokoVoip); self.config = json.decode(json.encode(config)); @@ -148,6 +162,9 @@ function TokoVoip.initialize(self) end if (not getPlayerData(self.serverId, "radio:talking")) then setPlayerData(self.serverId, "radio:talking", true, true); + if(self.config.enableDispatch and self.plugin_data.radioChannel <= self.config.dispatchRadioMaxChannel and self.plugin_data.radioChannel > 0) then + self:updatePlugin("talkStatusDS", true); + end end self:updateTokoVoipInfo(); if (lastTalkState == false and self.myChannels[self.plugin_data.radioChannel] and self.config.radioAnim) then @@ -164,6 +181,9 @@ function TokoVoip.initialize(self) self.plugin_data.radioTalking = false; if (getPlayerData(self.serverId, "radio:talking")) then setPlayerData(self.serverId, "radio:talking", false, true); + if(self.config.enableDispatch and self.plugin_data.radioChannel <= self.config.dispatchRadioMaxChannel and self.plugin_data.radioChannel > 0) then + self:updatePlugin("talkStatusDS", false); + end end self:updateTokoVoipInfo(); diff --git a/fivem_script/tokovoip_script/src/c_main.lua b/fivem_script/tokovoip_script/src/c_main.lua index 0084e7c5..c388bd45 100644 --- a/fivem_script/tokovoip_script/src/c_main.lua +++ b/fivem_script/tokovoip_script/src/c_main.lua @@ -32,8 +32,20 @@ local nuiLoaded = false local function setPlayerTalkingState(player, playerServerId) local talking = tonumber(getPlayerData(playerServerId, "voip:talking")); if (animStates[playerServerId] == 0 and talking == 1) then + if not HasAnimDictLoaded("mp_facial") then + RequestAnimDict("mp_facial"); + while not HasAnimDictLoaded("mp_facial") do + Citizen.Wait(5) + end + end PlayFacialAnim(GetPlayerPed(player), "mic_chatter", "mp_facial"); elseif (animStates[playerServerId] == 1 and talking == 0) then + if not HasAnimDictLoaded("facials@gen_male@base") then + RequestAnimDict("facials@gen_male@base"); + while not HasAnimDictLoaded("facials@gen_male@base") do + Citizen.Wait(5) + end + end PlayFacialAnim(GetPlayerPed(player), "mood_normal_1", "facials@gen_male@base"); end animStates[playerServerId] = talking; @@ -44,7 +56,7 @@ local function PlayRedMFacialAnimation(player, animDict, animName) while not HasAnimDictLoaded(animDict) do Wait(100) end - SetFacialIdleAnimOverride(player, animName, animDict) + SetFacialIdleAnimOverride(player, animName, animDict) end RegisterNUICallback("updatePluginData", function(data, cb) @@ -64,15 +76,39 @@ RegisterNUICallback("setPlayerTalking", function(data, cb) if (voip.talking == 1) then setPlayerData(voip.serverId, "voip:talking", 1, true); if (GetConvar("gametype") == "gta5") then + if not HasAnimDictLoaded("mp_facial") then + RequestAnimDict("mp_facial"); + while not HasAnimDictLoaded("mp_facial") do + Citizen.Wait(5) + end + end PlayFacialAnim(GetPlayerPed(PlayerId()), "mic_chatter", "mp_facial"); elseif (GetConvar("gametype") == "rdr3") then + if not HasAnimDictLoaded("face_human@gen_male@base") then + RequestAnimDict("face_human@gen_male@base"); + while not HasAnimDictLoaded("face_human@gen_male@base") do + Citizen.Wait(5) + end + end PlayRedMFacialAnimation(GetPlayerPed(PlayerId()), "face_human@gen_male@base", "mood_talking_normal"); end else setPlayerData(voip.serverId, "voip:talking", 0, true); if (GetConvar("gametype") == "gta5") then + if not HasAnimDictLoaded("facials@gen_male@base") then + RequestAnimDict("facials@gen_male@base"); + while not HasAnimDictLoaded("facials@gen_male@base") do + Citizen.Wait(5) + end + end PlayFacialAnim(PlayerPedId(), "mood_normal_1", "facials@gen_male@base"); elseif (GetConvar("gametype") == "rdr3") then + if not HasAnimDictLoaded("face_human@gen_male@base") then + RequestAnimDict("face_human@gen_male@base"); + while not HasAnimDictLoaded("face_human@gen_male@base") do + Citizen.Wait(5) + end + end PlayRedMFacialAnimation(PlayerPedId(), "face_human@gen_male@base", "mood_normal"); end end @@ -236,14 +272,6 @@ AddEventHandler("initializeVoip", function() -- Set targetped (used for spectator mod for admins) targetPed = GetPlayerPed(-1); - -- Request this stuff here only one time - if (GetConvar("gametype") == "gta5") then - RequestAnimDict("mp_facial"); - RequestAnimDict("facials@gen_male@base"); - elseif (GetConvar("gametype") == "rdr3") then - RequestAnimDict("face_human@gen_male@base"); - end - Citizen.Trace("TokoVoip: Initialized script (" .. scriptVersion .. ")\n"); local response; @@ -342,6 +370,9 @@ AddEventHandler("TokoVoip:onPlayerLeaveChannel", function(channelId, playerServe if (previousChannel ~= voip.plugin_data.radioChannel) then -- Update network data only if we actually changed radio channel setPlayerData(voip.serverId, "radio:channel", voip.plugin_data.radioChannel, true); + if (voip.config.enableDispatch and voip.config.dispatchRadioMaxChannel >= previousChannel) then + voip:leaveChannel(previousChannel, getPlayerData(voip.serverId, "voip:pluginUUID")); + end end -- Remote player left channel we are subscribed to @@ -361,6 +392,9 @@ AddEventHandler("TokoVoip:onPlayerJoinChannel", function(channelId, playerServer if (previousChannel ~= voip.plugin_data.radioChannel) then -- Update network data only if we actually changed radio channel setPlayerData(voip.serverId, "radio:channel", voip.plugin_data.radioChannel, true); + if (voip.config.enableDispatch and voip.config.dispatchRadioMaxChannel >= voip.plugin_data.radioChannel) then + voip:joinChannel(voip.plugin_data.radioChannel, getPlayerData(voip.serverId, "voip:pluginUUID")); + end end -- Remote player joined a channel we are subscribed to diff --git a/ws_server/index.js b/ws_server/index.js index 61234304..81d348c6 100644 --- a/ws_server/index.js +++ b/ws_server/index.js @@ -18,6 +18,10 @@ require('console-stamp')(console, { pattern: 'dd/mm/yyyy HH:MM:ss.l' }); let masterHeartbeatInterval; const clients = {}; +const channelsubs = {}; + +const ADS = {}; + const handshakes = {}; console.log(chalk`Like {cyan TokoVOIP} ? Consider supporting the development: {hex('#f96854') https://patreon.com/Itokoyamato}`); @@ -162,6 +166,22 @@ io.on('connection', async socket => { }); await registerHandshake(socket); socket.on('data', data => onIncomingData(socket, data)); + socket.on('channels', data => onChannelSub(socket, data)); + socket.on('talkingStatus', data => talkStatus(socket, data)); + } else if (socket.from === 'ds') { + socketHeartbeat(socket); + socket.on('updateClientIP', data => { + if (!data || !data.ip || !IPv4Regex.test(data.ip) || !clients[socket.uuid]) return; + if (lodash.get(clients, `[${socket.uuid}].fivem.socket`)) clients[socket.uuid].fivem.socket.clientIp = data.ip; + if (lodash.get(clients, `[${socket.uuid}].ts3.socket`)) clients[socket.uuid].ts3.socket.clientIp = data.ip; + }); + socket.ds = true; + await registerHandshake(socket); + socket.on('data', data => onIncomingDataDS(socket, data)); + socket.on('requestList', data => requestList(socket, data)); + socket.on('disconnect', (socket) => { + delete ADS[socket.uuid]; + }); } }); @@ -192,6 +212,14 @@ async function registerHandshake(socket) { throw e; } } + if(socket.ds) { + ADS[client.uuid] = { + channel: 1, + uuid: client.uuid, + talking: false + } + socket.emit('newDataList', channelsubs); + } socket.uuid = client.uuid; client.fivem.socket = socket; client.fivem.linkedAt = (new Date()).toISOString(); @@ -205,31 +233,139 @@ function setTS3Data(socket, data) { if (client.fivem.socket) { client.fivem.socket.emit('setTS3Data', client.ts3.data); } + if (data.key === 'talking') { + if(client.fivem.socket && client.fivem.socket.from === 'ds') { + ADS[socket.uuid].talking = data.value; + for (const [key, value] of Object.entries(clients)) { + if(value.uuid != socket.uuid) { + if(!value.fivem.data.Users.find(item => item.uuid === socket.uuid) && value.fivem.data.radioChannel == ADS[socket.uuid].channel) { + value.fivem.data.Users.push({ + uuid: socket.uuid, + radioEffect: true, + muted: !ADS[socket.uuid].talking, + radioTalking: ADS[socket.uuid].talking, + radioChannel: ADS[socket.uuid].channel, + volume: 100, + posX: 0, + posY: 0, + posZ: 0 + }); + client.ts3.socket.emit('processTokovoip', client.fivem.data); + } else if(value.fivem.data.radioChannel === ADS[socket.uuid].channel) { + value.fivem.data.Users.find(item => item.uuid === socket.uuid).radioTalking = ADS[socket.uuid].talking; + client.ts3.socket.emit('processTokovoip', client.fivem.data); + } + } else { + client.ts3.socket.emit('processTokovoip', client.fivem.data); + } + } + } + } +} + +function talkStatus(socket, data) { + dsclients = Object.values(clients).filter(item => item.fivem.socket.from === 'ds'); + let payload = { + status: data.status, + uuid: socket.uuid, + channel: socket.tokoData.radioChannel + }; + for (const [key, value] of Object.entries(dsclients)) { + value.fivem.socket.emit("talkStatusSet", payload); + } } function onIncomingData(socket, data) { const client = clients[socket.uuid]; if (!socket.uuid || !client || !client.ts3.socket || typeof data !== 'object') return; socket.tokoData = data; + for (const [key, value] of Object.entries(ADS)) { + if(socket.tokoData.radioChannel == value.channel) { + socket.tokoData.Users.push({ + uuid: key, + radioEffect: true, + muted: !value.talking, + radioTalking: value.talking, + radioChannel: value.channel, + volume: 0, + posX: 0, + posY: 0, + posZ: 0 + }); + } + } + client.fivem.data = socket.tokoData; + client.fivem.updatedAt = (new Date()).toISOString(); + client.ts3.socket.emit('processTokovoip', client.fivem.data); +} + +function onIncomingDataDS(socket, data) { + const client = clients[socket.uuid]; + if (!socket.uuid || !client || !client.ts3.socket) return; + socket.tokoData = data; + ADS[socket.uuid].channel = socket.tokoData.radioChannel; + for (const [key, value] of Object.entries(ADS)) { + if(socket.tokoData.radioChannel == value.channel && key !== socket.uuid) { + socket.tokoData.Users.push({ + uuid: key, + radioEffect: true, + muted: !value.talking, + radioTalking: value.talking, + radioChannel: value.channel, + volume: 0, + posX: 0, + posY: 0, + posZ: 0 + }); + } + } client.fivem.data = socket.tokoData; client.fivem.updatedAt = (new Date()).toISOString(); client.ts3.socket.emit('processTokovoip', client.fivem.data); } +function onChannelSub(socket, data) { + const client = clients[socket.uuid]; + if (!socket.uuid || !client || !client.ts3.socket) return; + if(data.type == 0) { + channelsubs[data.uuid] = data.channel; + } else { + delete channelsubs[data.uuid]; + } + dsclients = Object.values(clients).filter(item => item.fivem.socket.from === 'ds'); + for (const [key, value] of Object.entries(dsclients)) { + value.fivem.socket.emit("newDataList", channelsubs); + } +} + +function requestList(socket, data) { + socket.emit("newDataList", channelsubs); +} + async function onSocketDisconnect(socket) { log('log', chalk`{${socket.from === 'ts3' ? 'cyan' : 'yellow'} ${socket.from}} | Connection {red lost} - ${socket.safeIp}`); - if (socket.from === 'fivem') { + if (socket.from === 'fivem' || socket.from === 'ds') { if (handshakes[socket.clientIp]) delete handshakes[socket.clientIp]; } if (socket.uuid && clients[socket.uuid]) { const client = clients[socket.uuid]; delete clients[socket.uuid]; + if(channelsubs[socket.uuid]) { + delete channelsubs[socket.uuid]; + } const secondary = (socket.from === 'fivem') ? 'ts3' : 'fivem'; if (client[secondary].socket) { client[secondary].socket.emit('disconnectMessage', `${socket.from}Disconnected`); if (secondary === 'ts3') sleep(100) && client[secondary].socket.disconnect(true); else registerHandshake(client[secondary].socket); } + if (socket.from === 'ds') { + if (client['ts3'].socket) { + client.ts3.socket.emit("disconnectMessage", "Disconnected"); + sleep(100); + client['ts3'].socket.disconnect(true); + } + } } }