diff --git a/README.md b/README.md index 95eff31..a777386 100644 --- a/README.md +++ b/README.md @@ -76,9 +76,7 @@ It can be run as a standalone NodeJS application, on the same machine as your fi * Download [Node.js](https://nodejs.org/en/) * Open [config.js](https://github.com/Itokoyamato/TokoVOIP_TS3/blob/master/ws_server/config.js) * Change "`TSServer`" to your Teamspeak server `IPv4` - * Change "`FivemServerPort`" to your FiveM server `port` * If the ws-server is hosted on a separate machine: - Change "`FivemServerIP`" to your FiveM server `IPv4` * Open ws-server folder in cmd / terminal * Execute `npm i` * After its done run `node index.js` diff --git a/fivem_script/README.md b/fivem_script/README.md index 37c4bfc..12de683 100644 --- a/fivem_script/README.md +++ b/fivem_script/README.md @@ -146,3 +146,20 @@ If the channel name contains 'Call' it will be displayed as a call on the ingame If set to true, the data will be synced to all players, otherwise only to the client who requested it + +- ### setRadioVolume(volume) + + Changes the volume of voices over radio + + + + + + + + + + + + +
ParamstypeDetails
volumenumberChanges the radio volume
diff --git a/fivem_script/tokovoip_script/fxmanifest.lua b/fivem_script/tokovoip_script/fxmanifest.lua index 3b286ed..00c15bd 100644 --- a/fivem_script/tokovoip_script/fxmanifest.lua +++ b/fivem_script/tokovoip_script/fxmanifest.lua @@ -1,5 +1,6 @@ fx_version 'bodacious' -games { 'gta5' } +games { 'gta5', 'rdr3' } +rdr3_warning 'I acknowledge that this is a prerelease build of RedM, and I am aware my resources *will* become incompatible once RedM ships.' client_script "src/c_utils.lua" client_script "c_config.lua" @@ -15,4 +16,4 @@ ui_page "nui/index.html" files({ "nui/index.html", "nui/script.js", -}) \ No newline at end of file +}) diff --git a/fivem_script/tokovoip_script/nui/script.js b/fivem_script/tokovoip_script/nui/script.js index 987a519..939732d 100644 --- a/fivem_script/tokovoip_script/nui/script.js +++ b/fivem_script/tokovoip_script/nui/script.js @@ -57,11 +57,11 @@ function disconnect (src) { } } -function init (address) { +function init (address, serverId) { if (!address) return; endpoint = address; console.log('TokoVOIP: attempt new connection'); - websocket = new WebSocket(`ws://${endpoint}/socket.io/?EIO=3&transport=websocket&from=fivem`); + websocket = new WebSocket(`ws://${endpoint}/socket.io/?EIO=3&transport=websocket&from=fivem&serverId=${serverId}`); websocket.onopen = () => { updateWsState('FiveM', OK) diff --git a/fivem_script/tokovoip_script/src/c_main.lua b/fivem_script/tokovoip_script/src/c_main.lua index b500a7a..1702f4e 100644 --- a/fivem_script/tokovoip_script/src/c_main.lua +++ b/fivem_script/tokovoip_script/src/c_main.lua @@ -17,10 +17,11 @@ local targetPed; local useLocalPed = true; local isRunning = false; -local scriptVersion = "1.5.3"; +local scriptVersion = "1.5.4"; local animStates = {} local displayingPluginScreen = false; local HeadBone = 0x796e; +local radioVolume = 0; -------------------------------------------------------------------------------- -- Plugin functions @@ -37,6 +38,14 @@ local function setPlayerTalkingState(player, playerServerId) animStates[playerServerId] = talking; end +local function PlayRedMFacialAnimation(player, animDict, animName) + RequestAnimDict(animDict) + while not HasAnimDictLoaded(animDict) do + Wait(100) + end + SetFacialIdleAnimOverride(player, animName, animDict) +end + RegisterNUICallback("updatePluginData", function(data, cb) local payload = data.payload; if (voip[payload.key] == payload.data) then return end @@ -44,7 +53,7 @@ RegisterNUICallback("updatePluginData", function(data, cb) setPlayerData(voip.serverId, "voip:" .. payload.key, voip[payload.key], true); voip:updateConfig(); voip:updateTokoVoipInfo(true); - cb('ok') + cb('ok'); end); -- Receives data from the TS plugin on microphone toggle @@ -53,19 +62,27 @@ RegisterNUICallback("setPlayerTalking", function(data, cb) if (voip.talking == 1) then setPlayerData(voip.serverId, "voip:talking", 1, true); - PlayFacialAnim(GetPlayerPed(PlayerId()), "mic_chatter", "mp_facial"); + if (GetConvar("gametype") == "gta5") then + PlayFacialAnim(GetPlayerPed(PlayerId()), "mic_chatter", "mp_facial"); + elseif (GetConvar("gametype") == "rdr3") then + PlayRedMFacialAnimation(GetPlayerPed(PlayerId()), "face_human@gen_male@base", "mood_talking_normal"); + end else setPlayerData(voip.serverId, "voip:talking", 0, true); - PlayFacialAnim(PlayerPedId(), "mood_normal_1", "facials@gen_male@base"); + if (GetConvar("gametype") == "gta5") then + PlayFacialAnim(PlayerPedId(), "mood_normal_1", "facials@gen_male@base"); + elseif (GetConvar("gametype") == "rdr3") then + PlayRedMFacialAnimation(PlayerPedId(), "face_human@gen_male@base", "mood_normal"); + end end - cb('ok') + cb('ok'); end) local function clientProcessing() local playerList = voip.playerList; local usersdata = {}; local localHeading; - local ped = PlayerPedId() + local ped = PlayerPedId(); if (voip.headingType == 1) then localHeading = math.rad(GetEntityHeading(ped)); @@ -83,10 +100,17 @@ local function clientProcessing() for i=1, #playerList do local player = playerList[i]; local playerServerId = GetPlayerServerId(player); - if (GetPlayerPed(player) and voip.serverId ~= playerServerId) then - local playerPos = GetPedBoneCoords(GetPlayerPed(player), HeadBone); + local playerPed = GetPlayerPed(player); + + local playerTalking = getPlayerData(playerServerId, "voip:talking"); + + if (voip.serverId == playerServerId or not playerPed or not playerTalking or playerTalking == 0) then goto continue end + + do + local playerPos = GetPedBoneCoords(playerPed, HeadBone); local dist = #(localPos - playerPos); - if(dist > 40) then goto continue end + if (dist > voip.distance[3]) then goto continue end + if (not getPlayerData(playerServerId, "voip:mode")) then setPlayerData(playerServerId, "voip:mode", 1); @@ -102,10 +126,10 @@ local function clientProcessing() -- local angleToTarget = localHeading - math.atan(playerPos.y - localPos.y, playerPos.x - localPos.x); - -- Set player's default data - local tbl = { + -- Set player's position + local userData = { uuid = getPlayerData(playerServerId, "voip:pluginUUID"), - volume = -30, + volume = volume, muted = 1, radioEffect = false, posX = voip.plugin_data.enableStereoAudio and math.cos(angleToTarget) * dist or 0, @@ -115,69 +139,58 @@ local function clientProcessing() -- -- Process proximity - tbl.forceUnmuted = 0 if (dist >= voip.distance[mode]) then - tbl.muted = 1; + userData.muted = 1; else - tbl.volume = volume; - tbl.muted = 0; - tbl.forceUnmuted = 1 + userData.volume = volume; + userData.muted = 0; end - usersdata[#usersdata + 1] = tbl - setPlayerTalkingState(player, playerServerId); - ::continue:: + if (GetConvar("gametype") == "gta5") then + setPlayerTalkingState(player, playerServerId); + end + usersdata[#usersdata + 1] = userData; end + + ::continue:: end -- Process channels for _, channel in pairs(voip.myChannels) do for _, subscriber in pairs(channel.subscribers) do - if (subscriber == voip.serverId) then goto continue end + if (subscriber == voip.serverId) then goto channelContinue end local remotePlayerUsingRadio = getPlayerData(subscriber, "radio:talking"); local remotePlayerChannel = getPlayerData(subscriber, "radio:channel"); - local remotePlayerUuid = getPlayerData(subscriber, "voip:pluginUUID"); - local founduserData = nil - for k, v in pairs(usersdata) do - if(v.uuid == remotePlayerUuid) then - founduserData = v - end - end - - if not founduserData then - founduserData = { - uuid = remotePlayerUuid, - radioEffect = false, - resave = true, - volume = 0, - muted = 1 - } - end + if (not remotePlayerUsingRadio or remotePlayerChannel ~= channel.id) then goto channelContinue end + local remotePlayerUuid = getPlayerData(subscriber, "voip:pluginUUID"); - if (remotePlayerUsingRadio and remotePlayerChannel == channel.id) then - if (type(remotePlayerChannel) == "number" and remotePlayerChannel <= voip.config.radioClickMaxChannel) then - founduserData.radioEffect = true; - end + local userData = { + uuid = remotePlayerUuid, + radioEffect = false, + muted = false, + volume = radioVolume, + posX = 0, + posY = 0, + posZ = voip.plugin_data.enableStereoAudio and localPos.z or 0 + }; - founduserData.muted = false - founduserData.volume = 0; - founduserData.posX = 0; - founduserData.posY = 0; - founduserData.posZ = voip.plugin_data.enableStereoAudio and localPos.z or 0; + if ((type(remotePlayerChannel) == "number" and remotePlayerChannel <= voip.config.radioClickMaxChannel) or channel.radio) then + userData.radioEffect = true; end - if founduserData.forceUnmuted then - founduserData.muted = false; + for k, v in pairs(usersdata) do + if (v.uuid == remotePlayerUuid) then + usersdata[k] = userData; + goto channelContinue; + end end - if(founduserData.resave) then - usersdata[#usersdata + 1] = founduserData - end + usersdata[#usersdata + 1] = userData; - ::continue:: + ::channelContinue:: end end @@ -221,15 +234,31 @@ AddEventHandler("initializeVoip", function() -- Set targetped (used for spectator mod for admins) targetPed = GetPlayerPed(-1); - voip.processFunction = clientProcessing; -- Link the processing function that will be looped - voip:initialize(); -- Initialize the websocket and controls - voip:loop(); -- Start TokoVoip's loop + -- 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"); - -- Request this stuff here only one time - RequestAnimDict("mp_facial"); - RequestAnimDict("facials@gen_male@base"); + local response; + Citizen.CreateThread(function() + local function handler(serverId) response = serverId or "N/A"; end + RegisterNetEvent("TokoVoip:onClientGetServerId"); + AddEventHandler("TokoVoip:onClientGetServerId", handler); + TriggerServerEvent("TokoVoip:getServerId"); + while (not response) do Wait(5) end + + voip.fivemServerId = response; + print("TokoVoip: FiveM Server ID is " .. voip.fivemServerId); + + voip.processFunction = clientProcessing; -- Link the processing function that will be looped + voip:initialize(); -- Initialize the websocket and controls + voip:loop(); -- Start TokoVoip's loop + end); -- Debug data stuff if (voip.config.enableDebug) then @@ -343,6 +372,12 @@ function isPlayerInChannel(channel) end end +function setRadioVolume(volume) + radioVolume = volume; +end +RegisterNetEvent('TokoVoip:setRadioVolume'); +AddEventHandler('TokoVoip:setRadioVolume', setRadioVolume); + -------------------------------------------------------------------------------- -- Specific utils -------------------------------------------------------------------------------- @@ -371,3 +406,4 @@ end) exports("addPlayerToRadio", addPlayerToRadio); exports("removePlayerFromRadio", removePlayerFromRadio); exports("isPlayerInChannel", isPlayerInChannel); +exports("setRadioVolume", setRadioVolume); diff --git a/fivem_script/tokovoip_script/src/s_main.lua b/fivem_script/tokovoip_script/src/s_main.lua index 46e67a8..e40ed2e 100644 --- a/fivem_script/tokovoip_script/src/s_main.lua +++ b/fivem_script/tokovoip_script/src/s_main.lua @@ -15,6 +15,9 @@ -------------------------------------------------------------------------------- local channels = TokoVoipConfig.channels; +local serverId; + +SetConvarReplicated("gametype", GetConvar("GameName")); function addPlayerToRadio(channelId, playerServerId, radio) if (not channels[channelId]) then @@ -96,3 +99,13 @@ AddEventHandler('rconCommand', function(commandName, args) CancelEvent(); end end) + +function getServerId() TriggerClientEvent("TokoVoip:onClientGetServerId", source, serverId); end +RegisterServerEvent("TokoVoip:getServerId"); +AddEventHandler("TokoVoip:getServerId", getServerId); + +AddEventHandler("onResourceStart", function(resource) + if (resource ~= GetCurrentResourceName()) then return end; + serverId = randomString(32); + print("TokoVOIP FiveM Server ID: " .. serverId); +end); diff --git a/fivem_script/tokovoip_script/src/s_utils.lua b/fivem_script/tokovoip_script/src/s_utils.lua index 7466597..ae22a6a 100644 --- a/fivem_script/tokovoip_script/src/s_utils.lua +++ b/fivem_script/tokovoip_script/src/s_utils.lua @@ -50,3 +50,15 @@ function tablelength(T) for _ in pairs(T) do count = count + 1 end return count end + +local charset = {} do -- [0-9a-zA-Z] + for c = 48, 57 do table.insert(charset, string.char(c)) end + for c = 65, 90 do table.insert(charset, string.char(c)) end + for c = 97, 122 do table.insert(charset, string.char(c)) end +end + +function randomString(length) + if not length or length <= 0 then return '' end + math.randomseed(os.clock()^5) + return randomString(length - 1) .. charset[math.random(1, #charset)] +end diff --git a/ts3_package/package.ini b/ts3_package/package.ini index cae4e92..f01be2b 100644 --- a/ts3_package/package.ini +++ b/ts3_package/package.ini @@ -1,6 +1,6 @@ Name = TokoVOIP Type = Plugin Author = Itokoyamato, Thorsten Weinz (RadioFX) -Version = 1.5.2 +Version = 1.5.4 Platforms = win32, win64 Description = "This plugin is used to add a custom proximity chat and radio system to fiveM as well as radio effects thanks to the integration of the radioFX plugin." diff --git a/ts3_plugin/deps/teamspeak-plugin-radiofx b/ts3_plugin/deps/teamspeak-plugin-radiofx index 784a6c1..4a47aca 160000 --- a/ts3_plugin/deps/teamspeak-plugin-radiofx +++ b/ts3_plugin/deps/teamspeak-plugin-radiofx @@ -1 +1 @@ -Subproject commit 784a6c13c131036082a39b486523e4078ea3d816 +Subproject commit 4a47aca390486b3cf489ed1336d55b4907e04286 diff --git a/ts3_plugin/src/tokovoip.cpp b/ts3_plugin/src/tokovoip.cpp index 1500754..f31f446 100644 --- a/ts3_plugin/src/tokovoip.cpp +++ b/ts3_plugin/src/tokovoip.cpp @@ -599,7 +599,7 @@ string verifyTSServer() { return ""; } - httplib::Client cli("master.tokovoip.itokoyamato.net", 3000); + httplib::Client cli("master.tokovoip.itokoyamato.net"); string path = "/verify?address=" + string(serverIP); cli.set_follow_location(true); outputLog("Getting " + path); diff --git a/ws_server/Dockerfile b/ws_server/Dockerfile index 91482e2..ef01648 100644 --- a/ws_server/Dockerfile +++ b/ws_server/Dockerfile @@ -5,6 +5,6 @@ COPY . . RUN npm install --production -EXPOSE 3000 +EXPOSE 33250 CMD ["npm", "start"] diff --git a/ws_server/config.js b/ws_server/config.js index 71dc12a..f5c1951 100644 --- a/ws_server/config.js +++ b/ws_server/config.js @@ -3,21 +3,14 @@ module.exports = { TSServer: "127.0.0.1", //-- [REQUIRED] Port of the ws_server - //-- Make sure you open the port you specify below - WSServerPort: 3000, + //-- Make sure you open the port you specify below + //-- Please use a port above 30k as some networks block those below it + WSServerPort: 33250, //-- [OPTIONAL] IPv4 Address of the ws_server //-- Set by autoconfig // WSServerIP: "127.0.0.1", - //-- [OPTIONAL] IPv4 Adress of your FiveM server - //-- Set by autoconfig if you run ws_server as FXServer resource or standalone on the same machine - // FivemServerIP: "127.0.0.1", - - //-- [OPTIONAL] Port of your FiveM Server - //-- Set by autoconfig if you run ws_server as FXServer resource - // FivemServerPort: 32000, - //-- [OPTIONAL] Enable connection/disconnection logs enableLogs: false, }; diff --git a/ws_server/docker-compose.yml b/ws_server/docker-compose.yml index eda08e9..1f9e60b 100644 --- a/ws_server/docker-compose.yml +++ b/ws_server/docker-compose.yml @@ -8,9 +8,7 @@ services: container_name: ws_server restart: unless-stopped ports: - - "3000:3000" + - "33250:33250" environment: - TSServer= - - WSServerPort=3000 - - FivemServerIP= - - FivemServerPort= + - WSServerPort=33250 diff --git a/ws_server/index.js b/ws_server/index.js index b09078b..674d089 100644 --- a/ws_server/index.js +++ b/ws_server/index.js @@ -11,19 +11,13 @@ const config = require('./config.js'); const publicIp = require('public-ip'); let hostIP; -let runningOnFivem = false; - -try { - eval('GetResourcePath(GetCurrentResourceName())'); - runningOnFivem = true; -} catch(e) {} require('console-stamp')(console, { pattern: 'dd/mm/yyyy HH:MM:ss.l' }); let masterHeartbeatInterval; const clients = {}; -const handshakes = []; +const handshakes = {}; console.log(chalk`Like {cyan TokoVOIP} ? Consider supporting the development: {hex('#f96854') https://patreon.com/Itokoyamato}`); @@ -33,28 +27,16 @@ app.use(express.json()); config.TSServer = process.env.TSServer || config.TSServer; config.WSServerPort = parseInt(process.env.WSServerPort, 10) || parseInt(config.WSServerPort, 10); config.WSServerIP = process.env.WSServerIP || config.WSServerIP; - config.FivemServerIP = process.env.FivemServerIP || config.FivemServerIP; - config.FivemServerPort = parseInt(process.env.FivemServerPort, 10) || parseInt(config.FivemServerPort, 10); hostIP = await publicIp.v4(); if (config.WSServerIP === undefined) { config.WSServerIP = hostIP; console.log(chalk`{yellow AUTOCONFIG:} Setting {cyan WSServerIP} to {cyan ${hostIP}} (you can manually edit in config.js)`); await sleep(0); } - if (config.FivemServerIP === undefined) { - config.FivemServerIP = hostIP; - console.log(chalk`{yellow AUTOCONFIG:} Setting {cyan FivemServerIP} to {cyan ${hostIP}} (you can manually edit in config.js)`); - await sleep(0); - } - if (config.FivemServerPort === undefined && runningOnFivem) { - config.FivemServerPort = GetConvar('netPort'); - console.log(chalk`{yellow AUTOCONFIG:} Setting {cyan FivemServerPort} to {cyan ${config.FivemServerPort}} (you can manually edit in config.js)`); - await sleep(0); - } - if (!config.TSServer || !config.WSServerIP || !config.WSServerPort || !config.FivemServerIP || !config.FivemServerPort) { + if (!config.TSServer || !config.WSServerIP || !config.WSServerPort) { console.error(chalk`{red Config error: -Missing one of TSServer, WSServerIP, WSServerPort, FivemServerIP or FivemServerPort}` +Missing one of TSServer, WSServerIP or WSServerPort}` ); return; } @@ -73,18 +55,13 @@ Domain names are not supported.}` ); } - const FiveMURI = `http://${config.FivemServerIP}:${config.FivemServerPort}/info.json`; - await axios.get(FiveMURI) - .catch(e => { - configError = true - console.error(chalk`{red Config error: -FiveM server does not seem online. -Is it accessible from the internet ? -Make sure your configuration is correct and your ports are open.} -{cyan (${FiveMURI})}` + if (config.WSServerPort < 30000) { + console.error(chalk`{yellow Config warning: +It is advised to use a WSServerPort above 30k, some player networks block ports below it.}` ); - }); - const wsURI = `http://${config.WSServerIP}:${config.WSServerPort}` + } + + const wsURI = `http://${config.WSServerIP}:${config.WSServerPort}`; await axios.get(wsURI) .catch(e => { configError = true; @@ -96,14 +73,6 @@ Make sure your configuration is correct and your ports are open.} ); }); - if (config.FivemServerIP.includes('127.0.0.1') || config.FivemServerIP.includes('localhost')) { - configError = true; - console.error(chalk`{red Config error: -FiveMServerIP cannot be 127.0.0.1 or localhost. -It will be blocked by FiveM. -It has to be a valid public IPv4. -You might need to open your ports to run it locally.}`); - } if (configError) return; console.log(chalk`Everything looks {green good} ! Have fun`); @@ -124,7 +93,7 @@ app.get('/', (_, res) => { }); app.get('/playerbyip', (req, res) => { - const player = handshakes.find(item => item.clientIp === req.query.ip); + const player = handshakes[req.query.ip]; if (!player) return res.status(404).send(); return res.status(204).send(); }); @@ -136,9 +105,10 @@ http.on('upgrade', (req, socket) => { io.on('connection', async socket => { socket.from = socket.request._query.from; - socket.clientIp = socket.handshake.headers['x-forwared-for'] || socket.request.connection.remoteAddress.replace('::ffff:', ''); + socket.clientIp = socket.handshake.headers['x-forwarded-for'] || socket.request.connection.remoteAddress.replace('::ffff:', ''); socket.safeIp = Buffer.from(socket.clientIp).toString('base64'); - if (socket.clientIp.includes('::1') || socket.clientIp.includes('127.0.0.1')) socket.clientIp = process.env.LOCAL_IP; + if (socket.clientIp.includes('::1') || socket.clientIp.includes('127.0.0.1') || socket.clientIp.includes('192.168.')) socket.clientIp = hostIP; + socket.fivemServerId = socket.request._query.serverId; socket.on('disconnect', _ => onSocketDisconnect(socket)); @@ -149,8 +119,7 @@ io.on('connection', async socket => { let client = clients[socket.request._query.uuid]; socket.uuid = socket.request._query.uuid; - const handshake = handshakes.findIndex(item => item.clientIp === socket.clientIp); - if (handshake === -1) { + if (!handshakes[socket.clientIp]) { socket.emit('disconnectMessage', 'handshakeNotFound'); socket.disconnect(true); return; @@ -167,7 +136,7 @@ io.on('connection', async socket => { }; client.ts3.socket = socket; client.ts3.linkedAt = (new Date()).toISOString(); - handshakes.splice(handshake, 1); + delete handshakes[socket.clientIp]; log('log', chalk`{${socket.from === 'ts3' ? 'cyan' : 'yellow'} ${socket.from}} | Handshake {green successful} - ${socket.safeIp}`); @@ -184,7 +153,7 @@ io.on('connection', async socket => { }); async function registerHandshake(socket) { - handshakes.push(socket); + handshakes[socket.clientIp] = socket; let client; let tries = 0; while (!client) { @@ -232,8 +201,7 @@ function onIncomingData(socket, data) { async function onSocketDisconnect(socket) { log('log', chalk`{${socket.from === 'ts3' ? 'cyan' : 'yellow'} ${socket.from}} | Connection {red lost} - ${socket.safeIp}`); if (socket.from === 'fivem') { - const handshake = handshakes.findIndex(item => item == socket); - if (handshake !== -1) handshakes.splice(handshake, 1); + if (handshakes[socket.clientIp]) delete handshakes[socket.clientIp]; } if (socket.uuid && clients[socket.uuid]) { const client = clients[socket.uuid]; @@ -264,8 +232,6 @@ async function masterHeartbeat() { tsServer: config.TSServer, WSServerIP: config.WSServerIP, WSServerPort: config.WSServerPort, - FivemServerIP: config.FivemServerIP, - FivemServerPort: config.FivemServerPort, }) .then(_ => console.log('Heartbeat sent')) .catch(e => console.error('Sending heartbeat failed with error:', e.code)); diff --git a/ws_server/package.json b/ws_server/package.json index 9306495..5ea30b8 100644 --- a/ws_server/package.json +++ b/ws_server/package.json @@ -1,6 +1,6 @@ { "name": "ws_server", - "version": "1.5.2", + "version": "1.5.4", "description": "", "main": "index.js", "scripts": {