Skip to content

Commit

Permalink
voice_local: fixes and improvements (#575)
Browse files Browse the repository at this point in the history
  • Loading branch information
Fernando-A-Rocha authored Nov 21, 2024
1 parent 5d3d1b7 commit 53cf63d
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 171 deletions.
241 changes: 116 additions & 125 deletions [gameplay]/voice_local/client.lua
Original file line number Diff line number Diff line change
@@ -1,155 +1,146 @@
local fMinDistance = 0
local fMaxDistance = 25
local mathexp = math.exp
-- Remote events:
addEvent("voice_local:onClientPlayerVoiceStart", true)
addEvent("voice_local:onClientPlayerVoiceStop", true)
addEvent("voice_local:updateSettings", true)

-- Only starts handling player voices after receiving the settings from the server
local initialWaiting = true

local streamedPlayers = {}
local fDistDiff = fMinDistance - fMaxDistance
local localPlayerTalking = false

local sx, sy = guiGetScreenSize()
local nx, ny = sx / 1920, sy / 1080

local width = 108 * nx
local height = 180 * ny
local xOffset = 75 * nx
local yOffset = 50 * ny
local halfWidth = width / 2
local halfHeight = height / 2
local devSX, devSY = sx / 1920, sy / 1080
local iconWidth = 108 * devSX
local iconHalfWidth = iconWidth / 2
local iconHeight = 180 * devSY
local iconHalfHeight = iconHeight / 2
local iconTexture = dxCreateTexture("icon.png", "dxt5", true, "clamp")

local function drawTalkingIcon(player, camDistToPlayer)
local boneX, boneY, boneZ = getPedBonePosition(player, 8)
local screenX, screenY = getScreenFromWorldPosition(boneX, boneY, boneZ + 0.4)
if screenX and screenY then
local factor = 1 / camDistToPlayer
dxDrawImage(
screenX - iconHalfWidth * factor,
screenY - iconHalfHeight * factor,
iconWidth * factor,
iconHeight * factor,
iconTexture, 0, 0, 0, -1, false
)
end
end

local function handlePreRender()
local debugY = 50
local maxDistance = settings.maxVoiceDistance.value
local cameraX, cameraY, cameraZ = getCameraMatrix()
local localPlayerX, localPlayerY, localPlayerZ = getElementPosition(localPlayer)
for player, talking in pairs(streamedPlayers) do
local otherPlayerX, otherPlayerY, otherPlayerZ = getElementPosition(player)
local realDistanceToPlayer = getDistanceBetweenPoints3D(localPlayerX, localPlayerY, localPlayerZ, otherPlayerX, otherPlayerY, otherPlayerZ)
local playerVolume
if (realDistanceToPlayer >= maxDistance) then
playerVolume = 0.0
else
playerVolume = (1.0 - (realDistanceToPlayer / maxDistance)^2)
end

local icon = dxCreateTexture("icon.png", "dxt5", true, "clamp")
-- Voice voume is usually unfortunately very low, resulting in players
-- barely hearing others if we set the player voice volume to 1.0
-- So we need to increase it to like 6.0 to make it audible
playerVolume = playerVolume * settings.voiceSoundBoost.value

function preRender()
local x, y, z, lx, ly, lz = getCameraMatrix()
setSoundVolume(player, playerVolume)

for player, talking in pairs(streamedPlayers) do
if player and isElement(player) and getElementType(player) == "player" then
local x1, y1, z1 = getElementPosition(player)
local fDistance = getDistanceBetweenPoints3D(x, y, z, x1, y1, z1)

local fVolume
if (fDistance <= fMinDistance) then
fVolume = 100
elseif (fDistance >= fMaxDistance) then
fVolume = 0.0
else
fVolume = mathexp(-(fDistance - fMinDistance) * (5.0 / fDistDiff)) * 100
end

if isLineOfSightClear(x, y, z, x1, y1, z1, false, false, false, false, true, true, true, localPlayer) then
setSoundVolume(player, fVolume)
setSoundEffectEnabled(player, "compressor", false)
else
setSoundVolume(player, fVolume * 2)
setSoundEffectEnabled(player, "compressor", true)
end

if talking and isLineOfSightClear(x, y, z, x1, y1, z1, false, false, false, false, true, true, true, localPlayer) then
local boneX, boneY, boneZ = getPedBonePosition(player, 8)
local screenX, screenY = getScreenFromWorldPosition(boneX, boneY, boneZ + 0.5)
if screenX and screenY and fDistance < fMaxDistance then
fDistance = 1 / fDistance
dxDrawImage(screenX - halfWidth * fDistance, screenY - halfHeight * fDistance, width * fDistance, height * fDistance, icon, 0, 0, 0, -1, false)
end
end
if DEBUG_MODE then
dxDrawRectangle(20, debugY - 5, 300, 25, tocolor(0, 0, 0, 200))
dxDrawText(("%s | Distance: %.2f | Voice Volume: %.2f"):format(getPlayerName(player), realDistanceToPlayer, playerVolume), 30, debugY)
debugY = debugY + 15
end
end
end
addEventHandler("onClientPreRender", root, preRender)

function onStart()
local x, y, z = getElementPosition(localPlayer)
for _, player in ipairs(getElementsWithinRange(x, y, z, 250, "player")) do
if streamedPlayers[player] == nil then
streamedPlayers[player] = false
if talking and (settings.showTalkingIcon.value == true)
and realDistanceToPlayer < maxDistance
and isLineOfSightClear(cameraX, cameraY, cameraZ, otherPlayerX, otherPlayerY, otherPlayerZ, false, false, false, false, true, true, true, localPlayer) then
drawTalkingIcon(player, getDistanceBetweenPoints3D(cameraX, cameraY, cameraZ, otherPlayerX, otherPlayerY, otherPlayerZ))
end
end
triggerServerEvent("voice:setPlayerBroadcast", resourceRoot, localPlayer, streamedPlayers)
if localPlayerTalking and (settings.showTalkingIcon.value == true) then
drawTalkingIcon(localPlayer, getDistanceBetweenPoints3D(cameraX, cameraY, cameraZ, localPlayerX, localPlayerY, localPlayerZ))
end
end
addEventHandler("onClientResourceStart", resourceRoot, onStart, false)

function playerJoin()
if getElementType(source) == "player" then
if streamedPlayers[source] == nil then
streamedPlayers[source] = false
triggerServerEvent("voice:addToPlayerBroadcast", resourceRoot, localPlayer, source)
addEventHandler("onClientResourceStart", resourceRoot, function()
for _, player in pairs(getElementsByType("player", root, true)) do
if player ~= localPlayer and streamedPlayers[player] == nil then
setSoundVolume(player, 0)
streamedPlayers[player] = false
end
end
end
addEventHandler("onClientPlayerJoin", root, playerJoin)
triggerServerEvent("voice_local:setPlayerBroadcast", localPlayer, streamedPlayers)
end, false)

function playerQuit()
-- Handle remote/other player quit
addEventHandler("onClientPlayerQuit", root, function()
if streamedPlayers[source] ~= nil then
streamedPlayers[source] = nil
setSoundVolume(source, 0)
triggerServerEvent("voice:removePlayerBroadcast", resourceRoot, localPlayer, source)
triggerServerEvent("voice_local:removeFromPlayerBroadcast", localPlayer, source)
end
end
addEventHandler("onClientPlayerQuit", root, playerQuit)
end)

-- Code considers this event's problem @ "Note" box: https://wiki.multitheftauto.com/wiki/OnClientElementStreamIn
-- It should be modified if said behavior ever changes
function streamIn()
if (isElement(source) and getElementType(source) == "player" and isPedDead(source) == false) then
if streamedPlayers[source] == nil then
streamedPlayers[source] = false
triggerServerEvent("voice:addToPlayerBroadcast", resourceRoot, localPlayer, source)
end
end
end
addEventHandler("onClientElementStreamIn", root, streamIn)

-- To ensure table integrity, stream out & local player death into 2 separate, safer events
-- See the "Note" box at https://wiki.multitheftauto.com/wiki/OnClientElementStreamIn for reason why
-- Stream out event
function streamOut()
if (source ~= localPlayer and isElement(source) and getElementType(source) == "player") then
if streamedPlayers[source] ~= nil then
streamedPlayers[source] = nil
setSoundVolume(source, 0)
triggerServerEvent("voice:removePlayerBroadcast", resourceRoot, localPlayer, source)
end
end
end
addEventHandler("onClientElementStreamOut", root, streamOut)

-- Local player death event
function onWasted()
if streamedPlayers[localPlayer] ~= nil then
streamedPlayers[localPlayer] = nil
setSoundVolume(localPlayer, 0)
triggerServerEvent("voice:removePlayerBroadcast", resourceRoot, localPlayer, localPlayer)
end
end
addEventHandler("onClientPlayerWasted", localPlayer, onWasted)
addEventHandler("onClientElementStreamIn", root, function()
if source == localPlayer then return end
if not (isElement(source) and getElementType(source) == "player") then return end

function resourceStop()
triggerServerEvent("voice:removePlayerBroadcasts", resourceRoot, localPlayer, streamedPlayers)
streamedPlayers = {}
end
addEventHandler("onClientResourceStop", resourceRoot, resourceStop, false)
if isPedDead(source) then return end

function voiceStart(source)
if (isElement(source) and getElementType(source) == "player") then
if streamedPlayers[source] ~= nil then
streamedPlayers[source] = true
end
if streamedPlayers[source] == nil then
setSoundVolume(source, 0)
streamedPlayers[source] = false
triggerServerEvent("voice_local:addToPlayerBroadcast", localPlayer, source)
end
end
addEvent("voice_cl:onClientPlayerVoiceStart", true)
addEventHandler("voice_cl:onClientPlayerVoiceStart", resourceRoot, voiceStart)
end)
addEventHandler("onClientElementStreamOut", root, function()
if source == localPlayer then return end
if not (isElement(source) and getElementType(source) == "player") then return end

function voiceStop(source)
if (isElement(source) and getElementType(source) == "player") then
if streamedPlayers[source] ~= nil then
streamedPlayers[source] = false
end
if streamedPlayers[source] ~= nil then
setSoundVolume(source, 0)
streamedPlayers[source] = nil
triggerServerEvent("voice_local:removeFromPlayerBroadcast", localPlayer, source)
end
end
addEvent("voice_cl:onClientPlayerVoiceStop", true)
addEventHandler("voice_cl:onClientPlayerVoiceStop", resourceRoot, voiceStop)
end)

function table.empty(a)
if type(a) ~= "table" then
return false
-- Update player talking status (for displaying)
addEventHandler("voice_local:onClientPlayerVoiceStart", root, function(player)
if not (isElement(player) and getElementType(player) == "player") then return end

if player == localPlayer then
localPlayerTalking = true
elseif streamedPlayers[player] ~= nil then
streamedPlayers[player] = true
end
end)
addEventHandler("voice_local:onClientPlayerVoiceStop", root, function(player)
if not (isElement(player) and getElementType(player) == "player") then return end

if player == localPlayer then
localPlayerTalking = false
elseif streamedPlayers[player] ~= nil then
streamedPlayers[player] = false
end
end)

return next(a) == nil
end
-- Load the settings received from the server
addEventHandler("voice_local:updateSettings", localPlayer, function(settingsFromServer)
settings = settingsFromServer

if initialWaiting then
addEventHandler("onClientPreRender", root, handlePreRender, false)
initialWaiting = false
end
end, false)
11 changes: 10 additions & 1 deletion [gameplay]/voice_local/meta.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
<meta>
<info author="MTA contributors (github.com/multitheftauto/mtasa-resources)" version="1.0" name="Local voice chat" description="This resource implements a voice chat where only nearby players can talk to eachother" type="script" />
<info author="MTA contributors (github.com/multitheftauto/mtasa-resources)" version="1.1" name="Local voice chat" description="This resource implements a voice chat where only nearby players can talk to eachother" type="script" />

<min_mta_version server="1.3.0-0.04570"/>

<settings>
<setting name="*max_voice_distance" value="[25]"/>
<setting name="*voice_sound_boost" value="[6]"/>
<setting name="*show_talking_icon" value="[true]"/>
</settings>

<file src="icon.png"/>

<script src="shared.lua" type="shared"/>
<script src="client.lua" type="client"/>
<script src="server.lua" type="server"/>
</meta>
Loading

0 comments on commit 53cf63d

Please sign in to comment.