Skip to content

voice_local: fixes and improvements #575

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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