Saltychat Remove and PMA install

This commit is contained in:
Miho931 2025-06-30 19:26:56 +02:00
parent 0bff8ae174
commit 2fd3c1fe70
94 changed files with 8799 additions and 5199 deletions

View file

@ -0,0 +1,139 @@
voiceData = {}
radioData = {}
callData = {}
function defaultTable(source)
handleStateBagInitilization(source)
return {
radio = 0,
call = 0,
lastRadio = 0,
lastCall = 0
}
end
function handleStateBagInitilization(source)
local plyState = Player(source).state
if not plyState.pmaVoiceInit then
plyState:set('radio', GetConvarInt('voice_defaultRadioVolume', 30), true)
plyState:set('phone', GetConvarInt('voice_defaultPhoneVolume', 60), true)
plyState:set('proximity', {}, true)
plyState:set('callChannel', 0, true)
plyState:set('radioChannel', 0, true)
plyState:set('voiceIntent', 'speech', true)
-- We want to save voice inits because we'll automatically reinitalize calls and channels
plyState:set('pmaVoiceInit', true, false)
end
end
Citizen.CreateThread(function()
local plyTbl = GetPlayers()
for i = 1, #plyTbl do
local ply = tonumber(plyTbl[i])
voiceData[ply] = defaultTable(plyTbl[i])
end
Wait(5000)
local nativeAudio = GetConvar('voice_useNativeAudio', 'false')
local _3dAudio = GetConvar('voice_use3dAudio', 'false')
local _2dAudio = GetConvar('voice_use2dAudio', 'false')
local sendingRangeOnly = GetConvar('voice_useSendingRangeOnly', 'false')
local gameVersion = GetConvar('gamename', 'fivem')
-- handle no convars being set (default drag n' drop)
if
nativeAudio == 'false'
and _3dAudio == 'false'
and _2dAudio == 'false'
then
if gameVersion == 'fivem' then
SetConvarReplicated('voice_useNativeAudio', 'true')
if sendingRangeOnly == 'false' then
SetConvarReplicated('voice_useSendingRangeOnly', 'true')
end
logger.info('No convars detected for voice mode, defaulting to \'setr voice_useNativeAudio true\' and \'setr voice_useSendingRangeOnly true\'')
else
SetConvarReplicated('voice_use3dAudio', 'true')
if sendingRangeOnly == 'false' then
SetConvarReplicated('voice_useSendingRangeOnly', 'true')
end
logger.info('No convars detected for voice mode, defaulting to \'setr voice_use3dAudio true\' and \'setr voice_useSendingRangeOnly true\'')
end
elseif sendingRangeOnly == 'false' then
logger.warn('It\'s recommended to have \'voice_useSendingRangeOnly\' set to true you can do that with \'setr voice_useSendingRangeOnly true\', this prevents players who directly join the mumble server from broadcasting to players.')
end
if GetConvar('gamename', 'fivem') == 'rdr3' then
if nativeAudio == 'true' then
logger.warn("RedM doesn't currently support native audio, automatically switching to 3d audio. This also means that submixes will not work.")
SetConvarReplicated('voice_useNativeAudio', 'false')
SetConvarReplicated('voice_use3dAudio', 'true')
end
end
local radioVolume = GetConvarInt("voice_defaultRadioVolume", 30)
local phoneVolume = GetConvarInt("voice_defaultPhoneVolume", 60)
-- When casted to an integer these get set to 0 or 1, so warn on these values that they don't work
if
radioVolume == 0 or radioVolume == 1 or
phoneVolume == 0 or phoneVolume == 1
then
SetConvarReplicated("voice_defaultRadioVolume", 30)
SetConvarReplicated("voice_defaultPhoneVolume", 60)
for i = 1, 5 do
Wait(5000)
logger.warn("`voice_defaultRadioVolume` or `voice_defaultPhoneVolume` have their value set as a float, this is going to automatically be fixed but please update your convars.")
end
end
end)
AddEventHandler('playerJoining', function()
if not voiceData[source] then
voiceData[source] = defaultTable(source)
end
end)
AddEventHandler("playerDropped", function()
local source = source
if voiceData[source] then
local plyData = voiceData[source]
if plyData.radio ~= 0 then
removePlayerFromRadio(source, plyData.radio)
end
if plyData.call ~= 0 then
removePlayerFromCall(source, plyData.call)
end
voiceData[source] = nil
end
end)
if GetConvarInt('voice_externalDisallowJoin', 0) == 1 then
AddEventHandler('playerConnecting', function(_, _, deferral)
deferral.defer()
Wait(0)
deferral.done('This server is not accepting connections.')
end)
end
-- only meant for internal use so no documentation
function isValidPlayer(source)
return voiceData[source]
end
exports('isValidPlayer', isValidPlayer)
function getPlayersInRadioChannel(channel)
local returnChannel = radioData[channel]
if returnChannel then
return returnChannel
end
-- channel doesnt exist
return {}
end
exports('getPlayersInRadioChannel', getPlayersInRadioChannel)
exports('GetPlayersInRadioChannel', getPlayersInRadioChannel)

View file

@ -0,0 +1,94 @@
--- removes a player from the call for everyone in the call.
---@param source number the player to remove from the call
---@param callChannel number the call channel to remove them from
function removePlayerFromCall(source, callChannel)
logger.verbose('[phone] Removed %s from call %s', source, callChannel)
callData[callChannel] = callData[callChannel] or {}
for player, _ in pairs(callData[callChannel]) do
TriggerClientEvent('pma-voice:removePlayerFromCall', player, source)
end
callData[callChannel][source] = nil
voiceData[source] = voiceData[source] or defaultTable(source)
voiceData[source].call = 0
end
--- adds a player to a call
---@param source number the player to add to the call
---@param callChannel number the call channel to add them to
function addPlayerToCall(source, callChannel)
logger.verbose('[phone] Added %s to call %s', source, callChannel)
-- check if the channel exists, if it does set the varaible to it
-- if not create it (basically if not callData make callData)
callData[callChannel] = callData[callChannel] or {}
for player, _ in pairs(callData[callChannel]) do
-- don't need to send to the source because they're about to get sync'd!
if player ~= source then
TriggerClientEvent('pma-voice:addPlayerToCall', player, source)
end
end
callData[callChannel][source] = false
voiceData[source] = voiceData[source] or defaultTable(source)
voiceData[source].call = callChannel
TriggerClientEvent('pma-voice:syncCallData', source, callData[callChannel])
end
--- set the players call channel
---@param source number the player to set the call off
---@param _callChannel number the channel to set the player to (or 0 to remove them from any call channel)
function setPlayerCall(source, _callChannel)
if GetConvarInt('voice_enablePhones', 1) ~= 1 then return end
voiceData[source] = voiceData[source] or defaultTable(source)
local isResource = GetInvokingResource()
local plyVoice = voiceData[source]
local callChannel = tonumber(_callChannel)
if not callChannel then
-- only full error if its sent from another server-side resource
if isResource then
error(("'callChannel' expected 'number', got: %s"):format(type(_callChannel)))
else
return logger.warn("%s sent a invalid call, 'callChannel' expected 'number', got: %s", source,type(_callChannel))
end
end
if isResource then
-- got set in a export, need to update the client to tell them that their call
-- changed
TriggerClientEvent('pma-voice:clSetPlayerCall', source, callChannel)
end
Player(source).state.callChannel = callChannel
if callChannel ~= 0 and plyVoice.call == 0 then
addPlayerToCall(source, callChannel)
elseif callChannel == 0 then
removePlayerFromCall(source, plyVoice.call)
elseif plyVoice.call > 0 then
removePlayerFromCall(source, plyVoice.call)
addPlayerToCall(source, callChannel)
end
end
exports('setPlayerCall', setPlayerCall)
RegisterNetEvent('pma-voice:setPlayerCall', function(callChannel)
setPlayerCall(source, callChannel)
end)
function setTalkingOnCall(talking)
if GetConvarInt('voice_enablePhones', 1) ~= 1 then return end
local source = source
voiceData[source] = voiceData[source] or defaultTable(source)
local plyVoice = voiceData[source]
local callTbl = callData[plyVoice.call]
if callTbl then
logger.verbose('[phone] %s %s talking in call %s', source, talking and 'started' or 'stopped', plyVoice.call)
for player, _ in pairs(callTbl) do
if player ~= source then
logger.verbose('[call] Sending event to %s to tell them that %s is talking', player, source)
TriggerClientEvent('pma-voice:setTalkingOnCall', player, source, talking)
end
end
else
logger.verbose('[phone] %s tried to talk in call %s, but it doesnt exist.', source, plyVoice.call)
end
end
RegisterNetEvent('pma-voice:setTalkingOnCall', setTalkingOnCall)

View file

@ -0,0 +1,165 @@
local radioChecks = {}
--- checks if the player can join the channel specified
--- @param source number the source of the player
--- @param radioChannel number the channel they're trying to join
--- @return boolean if the user can join the channel
function canJoinChannel(source, radioChannel)
if radioChecks[radioChannel] then
return radioChecks[radioChannel](source)
end
return true
end
--- adds a check to the channel, function is expected to return a boolean of true or false
---@param channel number the channel to add a check to
---@param cb function the function to execute the check on
function addChannelCheck(channel, cb)
local channelType = type(channel)
local cbType = type(cb)
if channelType ~= "number" then
error(("'channel' expected 'number' got '%s'"):format(channelType))
end
if cbType ~= 'table' or not cb.__cfx_functionReference then
error(("'cb' expected 'function' got '%s'"):format(cbType))
end
radioChecks[channel] = cb
logger.info("%s added a check to channel %s", GetInvokingResource(), channel)
end
exports('addChannelCheck', addChannelCheck)
local function radioNameGetter_orig(source)
return GetPlayerName(source)
end
local radioNameGetter = radioNameGetter_orig
--- adds a check to the channel, function is expected to return a boolean of true or false
---@param cb function the function to execute the check on
function overrideRadioNameGetter(channel, cb)
local cbType = type(cb)
if cbType == 'table' and not cb.__cfx_functionReference then
error(("'cb' expected 'function' got '%s'"):format(cbType))
end
radioNameGetter = cb
logger.info("%s added a check to channel %s", GetInvokingResource(), channel)
end
exports('overrideRadioNameGetter', overrideRadioNameGetter)
--- adds a player to the specified radion channel
---@param source number the player to add to the channel
---@param radioChannel number the channel to set them to
function addPlayerToRadio(source, radioChannel)
if not canJoinChannel(source, radioChannel) then
-- remove the player from the radio client side
return TriggerClientEvent('pma-voice:removePlayerFromRadio', source, source)
end
logger.verbose('[radio] Added %s to radio %s', source, radioChannel)
-- check if the channel exists, if it does set the varaible to it
-- if not create it (basically if not radiodata make radiodata)
radioData[radioChannel] = radioData[radioChannel] or {}
local plyName = radioNameGetter(source)
for player, _ in pairs(radioData[radioChannel]) do
TriggerClientEvent('pma-voice:addPlayerToRadio', player, source, plyName)
end
voiceData[source] = voiceData[source] or defaultTable(source)
voiceData[source].radio = radioChannel
radioData[radioChannel][source] = false
TriggerClientEvent('pma-voice:syncRadioData', source, radioData[radioChannel], GetConvarInt("voice_syncPlayerNames", 0) == 1 and plyName)
end
--- removes a player from the specified channel
---@param source number the player to remove
---@param radioChannel number the current channel to remove them from
function removePlayerFromRadio(source, radioChannel)
logger.verbose('[radio] Removed %s from radio %s', source, radioChannel)
radioData[radioChannel] = radioData[radioChannel] or {}
for player, _ in pairs(radioData[radioChannel]) do
TriggerClientEvent('pma-voice:removePlayerFromRadio', player, source)
end
radioData[radioChannel][source] = nil
voiceData[source] = voiceData[source] or defaultTable(source)
voiceData[source].radio = 0
end
-- TODO: Implement this in a way that allows players to be on multiple channels
--- sets the players current radio channel
---@param source number the player to set the channel of
---@param _radioChannel number the radio channel to set them to (or 0 to remove them from radios)
function setPlayerRadio(source, _radioChannel)
if GetConvarInt('voice_enableRadios', 1) ~= 1 then return end
voiceData[source] = voiceData[source] or defaultTable(source)
local isResource = GetInvokingResource()
local plyVoice = voiceData[source]
local radioChannel = tonumber(_radioChannel)
if not radioChannel then
-- only full error if its sent from another server-side resource
if isResource then
error(("'radioChannel' expected 'number', got: %s"):format(type(_radioChannel)))
else
return logger.warn("%s sent a invalid radio, 'radioChannel' expected 'number', got: %s", source,type(_radioChannel))
end
end
if isResource then
-- got set in a export, need to update the client to tell them that their radio
-- changed
TriggerClientEvent('pma-voice:clSetPlayerRadio', source, radioChannel)
end
Player(source).state.radioChannel = radioChannel
if radioChannel ~= 0 and plyVoice.radio == 0 then
addPlayerToRadio(source, radioChannel)
elseif radioChannel == 0 then
removePlayerFromRadio(source, plyVoice.radio)
elseif plyVoice.radio > 0 then
removePlayerFromRadio(source, plyVoice.radio)
addPlayerToRadio(source, radioChannel)
end
end
exports('setPlayerRadio', setPlayerRadio)
RegisterNetEvent('pma-voice:setPlayerRadio', function(radioChannel)
setPlayerRadio(source, radioChannel)
end)
--- syncs the player talking across all radio members
---@param talking boolean sets if the palyer is talking.
function setTalkingOnRadio(talking)
if GetConvarInt('voice_enableRadios', 1) ~= 1 then return end
voiceData[source] = voiceData[source] or defaultTable(source)
local plyVoice = voiceData[source]
local radioTbl = radioData[plyVoice.radio]
if radioTbl then
radioTbl[source] = talking
logger.verbose('[radio] Set %s to talking: %s on radio %s',source, talking, plyVoice.radio)
for player, _ in pairs(radioTbl) do
if player ~= source then
TriggerClientEvent('pma-voice:setTalkingOnRadio', player, source, talking)
logger.verbose('[radio] Sync %s to let them know %s is %s',player, source, talking and 'talking' or 'not talking')
end
end
end
end
RegisterNetEvent('pma-voice:setTalkingOnRadio', setTalkingOnRadio)
AddEventHandler("onResourceStop", function(resource)
for channel, cfxFunctionRef in pairs(radioChecks) do
local functionRef = cfxFunctionRef.__cfx_functionReference
local functionResource = string.match(functionRef, resource)
if functionResource then
radioChecks[channel] = nil
logger.warn('Channel %s had its radio check removed because the resource that gave the checks stopped', channel)
end
end
if type(radioNameGetter) == "table" then
local radioRef = radioNameGetter.__cfx_functionReference
if radioRef then
local isResource = string.match(functionRef, resource)
if isResource then
radioNameGetter = radioNameGetter_orig
logger.warn('Radio name getter is resetting to default because the resource that gave the cb got turned off')
end
end
end
end)

View file

@ -0,0 +1,26 @@
let mutedPlayers = {}
// this is implemented in JS due to Lua's lack of a ClearTimeout
// muteply instead of mute because mute conflicts with rp-radio
RegisterCommand('muteply', (source, args) => {
const mutePly = parseInt(args[0])
const duration = parseInt(args[1]) || 900
if (mutePly && exports['pma-voice'].isValidPlayer(mutePly)) {
const isMuted = !MumbleIsPlayerMuted(mutePly);
Player(mutePly).state.muted = isMuted;
MumbleSetPlayerMuted(mutePly, isMuted);
emit('pma-voice:playerMuted', mutePly, source, isMuted, duration);
// since this is a toggle, if theres a mutedPlayers entry it can be assumed
// that they're currently muted, so we'll clear the timeout and unmute
if (mutedPlayers[mutePly]) {
clearTimeout(mutedPlayers[mutePly]);
MumbleSetPlayerMuted(mutePly, isMuted)
Player(mutePly).state.muted = isMuted;
return;
}
mutedPlayers[mutePly] = setTimeout(() => {
MumbleSetPlayerMuted(mutePly, !isMuted)
Player(mutePly).state.muted = !isMuted;
delete mutedPlayers[mutePly]
}, duration * 1000)
}
}, true)