Skip to content

Commit 53cf63d

Browse files
voice_local: fixes and improvements (#575)
1 parent 5d3d1b7 commit 53cf63d

File tree

4 files changed

+234
-171
lines changed

4 files changed

+234
-171
lines changed

[gameplay]/voice_local/client.lua

Lines changed: 116 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,155 +1,146 @@
1-
local fMinDistance = 0
2-
local fMaxDistance = 25
3-
local mathexp = math.exp
1+
-- Remote events:
2+
addEvent("voice_local:onClientPlayerVoiceStart", true)
3+
addEvent("voice_local:onClientPlayerVoiceStop", true)
4+
addEvent("voice_local:updateSettings", true)
5+
6+
-- Only starts handling player voices after receiving the settings from the server
7+
local initialWaiting = true
8+
49
local streamedPlayers = {}
5-
local fDistDiff = fMinDistance - fMaxDistance
10+
local localPlayerTalking = false
611

712
local sx, sy = guiGetScreenSize()
8-
local nx, ny = sx / 1920, sy / 1080
913

10-
local width = 108 * nx
11-
local height = 180 * ny
12-
local xOffset = 75 * nx
13-
local yOffset = 50 * ny
14-
local halfWidth = width / 2
15-
local halfHeight = height / 2
14+
local devSX, devSY = sx / 1920, sy / 1080
15+
local iconWidth = 108 * devSX
16+
local iconHalfWidth = iconWidth / 2
17+
local iconHeight = 180 * devSY
18+
local iconHalfHeight = iconHeight / 2
19+
local iconTexture = dxCreateTexture("icon.png", "dxt5", true, "clamp")
20+
21+
local function drawTalkingIcon(player, camDistToPlayer)
22+
local boneX, boneY, boneZ = getPedBonePosition(player, 8)
23+
local screenX, screenY = getScreenFromWorldPosition(boneX, boneY, boneZ + 0.4)
24+
if screenX and screenY then
25+
local factor = 1 / camDistToPlayer
26+
dxDrawImage(
27+
screenX - iconHalfWidth * factor,
28+
screenY - iconHalfHeight * factor,
29+
iconWidth * factor,
30+
iconHeight * factor,
31+
iconTexture, 0, 0, 0, -1, false
32+
)
33+
end
34+
end
35+
36+
local function handlePreRender()
37+
local debugY = 50
38+
local maxDistance = settings.maxVoiceDistance.value
39+
local cameraX, cameraY, cameraZ = getCameraMatrix()
40+
local localPlayerX, localPlayerY, localPlayerZ = getElementPosition(localPlayer)
41+
for player, talking in pairs(streamedPlayers) do
42+
local otherPlayerX, otherPlayerY, otherPlayerZ = getElementPosition(player)
43+
local realDistanceToPlayer = getDistanceBetweenPoints3D(localPlayerX, localPlayerY, localPlayerZ, otherPlayerX, otherPlayerY, otherPlayerZ)
44+
local playerVolume
45+
if (realDistanceToPlayer >= maxDistance) then
46+
playerVolume = 0.0
47+
else
48+
playerVolume = (1.0 - (realDistanceToPlayer / maxDistance)^2)
49+
end
1650

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

19-
function preRender()
20-
local x, y, z, lx, ly, lz = getCameraMatrix()
56+
setSoundVolume(player, playerVolume)
2157

22-
for player, talking in pairs(streamedPlayers) do
23-
if player and isElement(player) and getElementType(player) == "player" then
24-
local x1, y1, z1 = getElementPosition(player)
25-
local fDistance = getDistanceBetweenPoints3D(x, y, z, x1, y1, z1)
26-
27-
local fVolume
28-
if (fDistance <= fMinDistance) then
29-
fVolume = 100
30-
elseif (fDistance >= fMaxDistance) then
31-
fVolume = 0.0
32-
else
33-
fVolume = mathexp(-(fDistance - fMinDistance) * (5.0 / fDistDiff)) * 100
34-
end
35-
36-
if isLineOfSightClear(x, y, z, x1, y1, z1, false, false, false, false, true, true, true, localPlayer) then
37-
setSoundVolume(player, fVolume)
38-
setSoundEffectEnabled(player, "compressor", false)
39-
else
40-
setSoundVolume(player, fVolume * 2)
41-
setSoundEffectEnabled(player, "compressor", true)
42-
end
43-
44-
if talking and isLineOfSightClear(x, y, z, x1, y1, z1, false, false, false, false, true, true, true, localPlayer) then
45-
local boneX, boneY, boneZ = getPedBonePosition(player, 8)
46-
local screenX, screenY = getScreenFromWorldPosition(boneX, boneY, boneZ + 0.5)
47-
if screenX and screenY and fDistance < fMaxDistance then
48-
fDistance = 1 / fDistance
49-
dxDrawImage(screenX - halfWidth * fDistance, screenY - halfHeight * fDistance, width * fDistance, height * fDistance, icon, 0, 0, 0, -1, false)
50-
end
51-
end
58+
if DEBUG_MODE then
59+
dxDrawRectangle(20, debugY - 5, 300, 25, tocolor(0, 0, 0, 200))
60+
dxDrawText(("%s | Distance: %.2f | Voice Volume: %.2f"):format(getPlayerName(player), realDistanceToPlayer, playerVolume), 30, debugY)
61+
debugY = debugY + 15
5262
end
53-
end
54-
end
55-
addEventHandler("onClientPreRender", root, preRender)
5663

57-
function onStart()
58-
local x, y, z = getElementPosition(localPlayer)
59-
for _, player in ipairs(getElementsWithinRange(x, y, z, 250, "player")) do
60-
if streamedPlayers[player] == nil then
61-
streamedPlayers[player] = false
64+
if talking and (settings.showTalkingIcon.value == true)
65+
and realDistanceToPlayer < maxDistance
66+
and isLineOfSightClear(cameraX, cameraY, cameraZ, otherPlayerX, otherPlayerY, otherPlayerZ, false, false, false, false, true, true, true, localPlayer) then
67+
drawTalkingIcon(player, getDistanceBetweenPoints3D(cameraX, cameraY, cameraZ, otherPlayerX, otherPlayerY, otherPlayerZ))
6268
end
6369
end
64-
triggerServerEvent("voice:setPlayerBroadcast", resourceRoot, localPlayer, streamedPlayers)
70+
if localPlayerTalking and (settings.showTalkingIcon.value == true) then
71+
drawTalkingIcon(localPlayer, getDistanceBetweenPoints3D(cameraX, cameraY, cameraZ, localPlayerX, localPlayerY, localPlayerZ))
72+
end
6573
end
66-
addEventHandler("onClientResourceStart", resourceRoot, onStart, false)
6774

68-
function playerJoin()
69-
if getElementType(source) == "player" then
70-
if streamedPlayers[source] == nil then
71-
streamedPlayers[source] = false
72-
triggerServerEvent("voice:addToPlayerBroadcast", resourceRoot, localPlayer, source)
75+
addEventHandler("onClientResourceStart", resourceRoot, function()
76+
for _, player in pairs(getElementsByType("player", root, true)) do
77+
if player ~= localPlayer and streamedPlayers[player] == nil then
78+
setSoundVolume(player, 0)
79+
streamedPlayers[player] = false
7380
end
7481
end
75-
end
76-
addEventHandler("onClientPlayerJoin", root, playerJoin)
82+
triggerServerEvent("voice_local:setPlayerBroadcast", localPlayer, streamedPlayers)
83+
end, false)
7784

78-
function playerQuit()
85+
-- Handle remote/other player quit
86+
addEventHandler("onClientPlayerQuit", root, function()
7987
if streamedPlayers[source] ~= nil then
8088
streamedPlayers[source] = nil
81-
setSoundVolume(source, 0)
82-
triggerServerEvent("voice:removePlayerBroadcast", resourceRoot, localPlayer, source)
89+
triggerServerEvent("voice_local:removeFromPlayerBroadcast", localPlayer, source)
8390
end
84-
end
85-
addEventHandler("onClientPlayerQuit", root, playerQuit)
91+
end)
8692

8793
-- Code considers this event's problem @ "Note" box: https://wiki.multitheftauto.com/wiki/OnClientElementStreamIn
8894
-- It should be modified if said behavior ever changes
89-
function streamIn()
90-
if (isElement(source) and getElementType(source) == "player" and isPedDead(source) == false) then
91-
if streamedPlayers[source] == nil then
92-
streamedPlayers[source] = false
93-
triggerServerEvent("voice:addToPlayerBroadcast", resourceRoot, localPlayer, source)
94-
end
95-
end
96-
end
97-
addEventHandler("onClientElementStreamIn", root, streamIn)
98-
99-
-- To ensure table integrity, stream out & local player death into 2 separate, safer events
100-
-- See the "Note" box at https://wiki.multitheftauto.com/wiki/OnClientElementStreamIn for reason why
101-
-- Stream out event
102-
function streamOut()
103-
if (source ~= localPlayer and isElement(source) and getElementType(source) == "player") then
104-
if streamedPlayers[source] ~= nil then
105-
streamedPlayers[source] = nil
106-
setSoundVolume(source, 0)
107-
triggerServerEvent("voice:removePlayerBroadcast", resourceRoot, localPlayer, source)
108-
end
109-
end
110-
end
111-
addEventHandler("onClientElementStreamOut", root, streamOut)
112-
113-
-- Local player death event
114-
function onWasted()
115-
if streamedPlayers[localPlayer] ~= nil then
116-
streamedPlayers[localPlayer] = nil
117-
setSoundVolume(localPlayer, 0)
118-
triggerServerEvent("voice:removePlayerBroadcast", resourceRoot, localPlayer, localPlayer)
119-
end
120-
end
121-
addEventHandler("onClientPlayerWasted", localPlayer, onWasted)
95+
addEventHandler("onClientElementStreamIn", root, function()
96+
if source == localPlayer then return end
97+
if not (isElement(source) and getElementType(source) == "player") then return end
12298

123-
function resourceStop()
124-
triggerServerEvent("voice:removePlayerBroadcasts", resourceRoot, localPlayer, streamedPlayers)
125-
streamedPlayers = {}
126-
end
127-
addEventHandler("onClientResourceStop", resourceRoot, resourceStop, false)
99+
if isPedDead(source) then return end
128100

129-
function voiceStart(source)
130-
if (isElement(source) and getElementType(source) == "player") then
131-
if streamedPlayers[source] ~= nil then
132-
streamedPlayers[source] = true
133-
end
101+
if streamedPlayers[source] == nil then
102+
setSoundVolume(source, 0)
103+
streamedPlayers[source] = false
104+
triggerServerEvent("voice_local:addToPlayerBroadcast", localPlayer, source)
134105
end
135-
end
136-
addEvent("voice_cl:onClientPlayerVoiceStart", true)
137-
addEventHandler("voice_cl:onClientPlayerVoiceStart", resourceRoot, voiceStart)
106+
end)
107+
addEventHandler("onClientElementStreamOut", root, function()
108+
if source == localPlayer then return end
109+
if not (isElement(source) and getElementType(source) == "player") then return end
138110

139-
function voiceStop(source)
140-
if (isElement(source) and getElementType(source) == "player") then
141-
if streamedPlayers[source] ~= nil then
142-
streamedPlayers[source] = false
143-
end
111+
if streamedPlayers[source] ~= nil then
112+
setSoundVolume(source, 0)
113+
streamedPlayers[source] = nil
114+
triggerServerEvent("voice_local:removeFromPlayerBroadcast", localPlayer, source)
144115
end
145-
end
146-
addEvent("voice_cl:onClientPlayerVoiceStop", true)
147-
addEventHandler("voice_cl:onClientPlayerVoiceStop", resourceRoot, voiceStop)
116+
end)
148117

149-
function table.empty(a)
150-
if type(a) ~= "table" then
151-
return false
118+
-- Update player talking status (for displaying)
119+
addEventHandler("voice_local:onClientPlayerVoiceStart", root, function(player)
120+
if not (isElement(player) and getElementType(player) == "player") then return end
121+
122+
if player == localPlayer then
123+
localPlayerTalking = true
124+
elseif streamedPlayers[player] ~= nil then
125+
streamedPlayers[player] = true
126+
end
127+
end)
128+
addEventHandler("voice_local:onClientPlayerVoiceStop", root, function(player)
129+
if not (isElement(player) and getElementType(player) == "player") then return end
130+
131+
if player == localPlayer then
132+
localPlayerTalking = false
133+
elseif streamedPlayers[player] ~= nil then
134+
streamedPlayers[player] = false
152135
end
136+
end)
153137

154-
return next(a) == nil
155-
end
138+
-- Load the settings received from the server
139+
addEventHandler("voice_local:updateSettings", localPlayer, function(settingsFromServer)
140+
settings = settingsFromServer
141+
142+
if initialWaiting then
143+
addEventHandler("onClientPreRender", root, handlePreRender, false)
144+
initialWaiting = false
145+
end
146+
end, false)

[gameplay]/voice_local/meta.xml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
<meta>
2-
<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" />
2+
<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" />
3+
4+
<min_mta_version server="1.3.0-0.04570"/>
5+
6+
<settings>
7+
<setting name="*max_voice_distance" value="[25]"/>
8+
<setting name="*voice_sound_boost" value="[6]"/>
9+
<setting name="*show_talking_icon" value="[true]"/>
10+
</settings>
311

412
<file src="icon.png"/>
513

14+
<script src="shared.lua" type="shared"/>
615
<script src="client.lua" type="client"/>
716
<script src="server.lua" type="server"/>
817
</meta>

0 commit comments

Comments
 (0)