diff --git a/server-data/resources/[phone]/pma-voice/.gitignore b/server-data/resources/[phone]/pma-voice/.gitignore new file mode 100644 index 000000000..2e2d6d210 --- /dev/null +++ b/server-data/resources/[phone]/pma-voice/.gitignore @@ -0,0 +1,2 @@ +.idea +.vscode \ No newline at end of file diff --git a/server-data/resources/[phone]/pma-voice/README.md b/server-data/resources/[phone]/pma-voice/README.md index c9be51d16..2e130d725 100644 --- a/server-data/resources/[phone]/pma-voice/README.md +++ b/server-data/resources/[phone]/pma-voice/README.md @@ -1,3 +1,10 @@ +## PLEASE NOTE: Currently master branch has some breaking changes + +If you previously used `voice_defaultPhoneVolume` you will instead need to use `voice_defaultCallVolume` +If you previously used `voice_enablePhones` you will instead need to use `voice_enableCalls` + +If you were previously using the state bag getter `Player(source).state.phone` you will instead need to use `Player(source).state.call` + # pma-voice A voice system designed around the use of FiveM/RedM internal mumble server. @@ -11,7 +18,7 @@ Please report any issues you have in the GitHub [Issues](https://github.com/Avar This script is not compatible with other voice systems (duh), that means if you have vMenus voice chat you will **have** to [disable](https://docs.vespura.com/vmenu/faq/#q-how-do-i-disable-voice-chat) it. -Please do not override `NetworkSetTalkerProximity`, `MumbleSetAudioInputDistance`, `MumbleSetAudioOutputDistance` or `NetworkSetVoiceActive` in any of your other scripts as there have been cases where it breaks pma-voice. +Please do not override `NetworkSetTalkerProximity`, `MumbleSetTalkerProximity`, `MumbleSetAudioInputDistance`, `MumbleSetAudioOutputDistance` or `NetworkSetVoiceActive` in any of your other scripts as there have been cases where it breaks pma-voice. # Credits @@ -30,8 +37,8 @@ Native audio will not work on RedM, you will have to use 3d audio. | ConVar | Default | Description | Parameter(s) | |----------------------------|---------|---------------------------------------------------------------|--------------| -| voice_useNativeAudio | false | **This will not work for RedM** Uses the games native audio, will add 3d sound, echo, reverb, and more. **Required for submixs** | boolean | -| voice_use2dAudio | false | Uses 2d audio, will result in same volume sound no matter where they're at until they leave proximity. | boolean +| voice_useNativeAudio | false | Uses the games native audio, will add 3d sound, echo, reverb, and more. **Required for submixs** | boolean | +| voice_use2dAudio | false | Uses 2d audio, will result in same volume sound no matter where they're at until they leave proximity. | boolean | voice_use3dAudio | false | Uses 3d audio | boolean | | voice_useSendingRangeOnly | false | Only allows you to hear people within your hear/send range, prevents people from connecting to your mumble server and trolling. | boolean | @@ -55,17 +62,17 @@ All of the configs here are set using `setr [voice_configOption] [int]` OR `setr | voice_enableProximityCycle | 1 | Enables the usage of the F11 proximity key, if disabled players are stuck on the first proximity | int | | voice_defaultCycle | F11 | The default key to cycle the players proximity. You can find a list of valid keys [in the Cfx docs](https://docs.fivem.net/docs/game-references/input-mapper-parameter-ids/keyboard/) | string | | voice_defaultRadioVolume | 30 | The default volume to set the radio to (has to be between 1 and 100) *NOTE: Only new joins will have the new value, players that already joined will not.* | float | -| voice_defaultPhoneVolume | 60 | The default volume to set the phone to (has to be between 1 and 100) *NOTE: Only new joins will have the new value, players that already joined will not.* | float | +| voice_defaultCallVolume | 60 | The default volume to set the call to (has to be between 1 and 100) *NOTE: Only new joins will have the new value, players that already joined will not.* | float | | voice_defaultVoiceMode | 2 | Default proximity voice value when player joins server. (Voice Modes; 1:Whisper, 2:Normal, 3:Shouting) | int | -### Phone & Radio +### Call & Radio | ConVar | Default | Description | Parameter(s) | |-------------------------|---------|--------------------------------------------------------------------|--------------| | voice_enableRadios | 1 | Enables the radio sub-modules | int | -| voice_enablePhones | 1 | Enables the phone sub-modules | int | -| voice_enableSubmix | 1 | Enables the submix which adds a radio/phone style submix to their voice **NOTE: Submixs require native audio** | int | -| voice_enableRadioAnim | 0 | Enables (grab shoulder mic) animation while talking on the radio. | int | +| voice_enableCalls | 1 | Enables the call sub-modules | int | +| voice_enableSubmix | 1 | Enables the submix which adds a radio/call style submix to their voice **NOTE: Submixs require native audio** | int | +| voice_enableRadioAnim | 1 | Enables (grab shoulder mic) animation while talking on the radio. | int | | voice_defaultRadio | LMENU | The default key to use the radio. You can find a list of valid keys [in the FiveM docs](https://docs.fivem.net/docs/game-references/input-mapper-parameter-ids/keyboard/) | string | ### Sync @@ -100,7 +107,7 @@ This would only allow the superadmin group to mute players. #### Client ##### Setters - + | Export | Description | Parameter(s) | |---------------------|-----------------------------|--------------| | [setVoiceProperty](docs/client-setters/setVoiceProperty.md) | Set config options | string, any | @@ -130,7 +137,7 @@ Supported from mumble-voip / toko-voip #### Getters -The majority of setters are done through player states, while a small +The majority of setters are done through player states. | State Bag | Description | Return Type | @@ -138,6 +145,7 @@ The majority of setters are done through player states, while a small | [proximity](docs/state-getters/stateBagGetters.md) | Returns a table with the mode index, distance, and mode name | table | | [radioChannel](docs/state-getters/stateBagGetters.md) | Returns the players current radio channel, or 0 for none | int | | [callChannel](docs/state-getters/stateBagGetters.md) | Returns the players current call channel, or 0 for none | int | +| [disableRadio](docs/state-getters/stateBagGetters.md) | Returns if the players radio is currently disabled, or 0 if its not. This is expected to be use as a bitwise, do *not* use a bool | int | #### Events @@ -172,6 +180,19 @@ You can access the state with `Player(source).state['state bag here']` | [radioChannel](docs/state-getters/stateBagGetters.md) | Returns the players current radio channel, or 0 for none | int | | [callChannel](docs/state-getters/stateBagGetters.md) | Returns the players current call channel, or 0 for none | int | | [voiceIntent](docs/state-getters/stateBagGetters.md) | Returns the players current voice intent, either 'speech' or 'music' | string | +| [disableRadio](docs/state-getters/stateBagGetters.md) | Returns if the players radio is currently disabled, or 0 if its not. This is expected to be use as a bitwise, do *not* use a bool | int | + +```ts +enum DisabledRadioStates { + Enabled = 0, + IsDead = 1, + IsCuffed = 2, + IsPdCuffed = 4, + IsUnderWater = 8, + DoesntHaveItem = 16, + PlayerDisabledRadio = 32, +} +``` ###### Exports diff --git a/server-data/resources/[phone]/pma-voice/TODO.md b/server-data/resources/[phone]/pma-voice/TODO.md index cca1dd885..0371ce297 100644 --- a/server-data/resources/[phone]/pma-voice/TODO.md +++ b/server-data/resources/[phone]/pma-voice/TODO.md @@ -1,10 +1,10 @@ ## TODO -- [ ] Rename everything that uses 'phone' to 'call' for consistency. -- [ ] Ability to display radio members on the client -- [ ] Use commands to define voiceModes in shared.lua and only leave debug logs in shared.lua +- [ ] Ability to display radio members on the client. +- [ ] Use commands to define voiceModes in shared.lua and only leave debug logs in shared.lua. - [ ] Convert the UI to React. -- [ ] Multiple radio channels +- [ ] Multiple radio channels. ## DONE -- [ x ] Implement a easy way to get the players current radio channel on the server -- [ x ] Add the ability to override proximity with exports \ No newline at end of file +- [ x ] Implement a easy way to get the players current radio channel on the server. +- [ x ] Add the ability to override proximity with exports. +- [ x ] Rename everything that uses 'phone' to 'call' for consistency. diff --git a/server-data/resources/[phone]/pma-voice/client/commands.lua b/server-data/resources/[phone]/pma-voice/client/commands.lua index a1c001d71..79713c88d 100644 --- a/server-data/resources/[phone]/pma-voice/client/commands.lua +++ b/server-data/resources/[phone]/pma-voice/client/commands.lua @@ -1,6 +1,6 @@ local wasProximityDisabledFromOverride = false disableProximityCycle = false -RegisterCommand('setvoiceintent', function(_, args) +RegisterCommand('setvoiceintent', function(source, args) if GetConvarInt('voice_allowSetIntent', 1) == 1 then local intent = args[1] if intent == 'speech' then @@ -11,15 +11,24 @@ RegisterCommand('setvoiceintent', function(_, args) LocalPlayer.state:set('voiceIntent', intent, true) end end) +TriggerEvent('chat:addSuggestion', '/setvoiceintent', 'Sets the players voice intent', { + { + name = "intent", + help = "speech is default and enables noise suppression & high pass filter, music disables both of these." + }, +}) -- TODO: Better implementation of this? RegisterCommand('vol', function(_, args) if not args[1] then return end setVolume(tonumber(args[1])) end) +TriggerEvent('chat:addSuggestion', '/vol', 'Sets the radio/phone volume', { + { name = "volume", help = "A range between 1-100 on how loud you want them to be" }, +}) exports('setAllowProximityCycleState', function(state) - type_check({state, "boolean"}) + type_check({ state, "boolean" }) disableProximityCycle = state end) @@ -38,7 +47,7 @@ function setProximityState(proximityRange, isCustom) end exports("overrideProximityRange", function(range, disableCycle) - type_check({range, "number"}) + type_check({ range, "number" }) setProximityState(range, true) if disableCycle then disableProximityCycle = true diff --git a/server-data/resources/[phone]/pma-voice/client/events.lua b/server-data/resources/[phone]/pma-voice/client/events.lua index c76dbfd77..918673db2 100644 --- a/server-data/resources/[phone]/pma-voice/client/events.lua +++ b/server-data/resources/[phone]/pma-voice/client/events.lua @@ -3,26 +3,28 @@ function handleInitialState() MumbleSetTalkerProximity(voiceModeData[1] + 0.0) MumbleClearVoiceTarget(voiceTarget) MumbleSetVoiceTarget(voiceTarget) - MumbleSetVoiceChannel(playerServerId) + MumbleSetVoiceChannel(LocalPlayer.state.assignedChannel) - while MumbleGetVoiceChannelFromServerId(playerServerId) ~= playerServerId do + while MumbleGetVoiceChannelFromServerId(playerServerId) ~= LocalPlayer.state.assignedChannel do Wait(250) + MumbleSetVoiceChannel(LocalPlayer.state.assignedChannel) end - MumbleAddVoiceTargetChannel(voiceTarget, playerServerId) + MumbleAddVoiceTargetChannel(voiceTarget, LocalPlayer.state.assignedChannel) addNearbyPlayers() end AddEventHandler('mumbleConnected', function(address, isReconnecting) - logger.info('Connected to mumble server with address of %s, is this a reconnect %s', GetConvarInt('voice_hideEndpoints', 1) == 1 and 'HIDDEN' or address, isReconnecting) + logger.info('Connected to mumble server with address of %s, is this a reconnect %s', + GetConvarInt('voice_hideEndpoints', 1) == 1 and 'HIDDEN' or address, isReconnecting) logger.log('Connecting to mumble, setting targets.') -- don't try to set channel instantly, we're still getting data. local voiceModeData = Cfg.voiceModes[mode] LocalPlayer.state:set('proximity', { index = mode, - distance = voiceModeData[1], + distance = voiceModeData[1], mode = voiceModeData[2], }, true) @@ -32,10 +34,11 @@ AddEventHandler('mumbleConnected', function(address, isReconnecting) end) AddEventHandler('mumbleDisconnected', function(address) - logger.info('Disconnected from mumble server with address of %s', GetConvarInt('voice_hideEndpoints', 1) == 1 and 'HIDDEN' or address) + logger.info('Disconnected from mumble server with address of %s', + GetConvarInt('voice_hideEndpoints', 1) == 1 and 'HIDDEN' or address) end) -- TODO: Convert the last Cfg to a Convar, while still keeping it simple. AddEventHandler('pma-voice:settingsCallback', function(cb) cb(Cfg) -end) \ No newline at end of file +end) diff --git a/server-data/resources/[phone]/pma-voice/client/init/init.lua b/server-data/resources/[phone]/pma-voice/client/init/init.lua index 69a500274..8653445cf 100644 --- a/server-data/resources/[phone]/pma-voice/client/init/init.lua +++ b/server-data/resources/[phone]/pma-voice/client/init/init.lua @@ -1,4 +1,3 @@ - AddEventHandler('onClientResourceStart', function(resource) if resource ~= GetCurrentResourceName() then return @@ -10,18 +9,19 @@ AddEventHandler('onClientResourceStart', function(resource) local success = pcall(function() local micClicksKvp = GetResourceKvpString('pma-voice_enableMicClicks') if not micClicksKvp then - SetResourceKvp('pma-voice_enableMicClicks', tostring(true)) + SetResourceKvp('pma-voice_enableMicClicks', "true") else if micClicksKvp ~= 'true' and micClicksKvp ~= 'false' then - error('Invalid Kvp, throwing error for automatic cleaning') + error('Invalid Kvp, throwing error for automatic fix') end micClicks = micClicksKvp end end) if not success then - logger.warn('Failed to load resource Kvp, likely was inappropriately modified by another server, resetting the Kvp.') - SetResourceKvp('pma-voice_enableMicClicks', tostring(true)) + logger.warn( + 'Failed to load resource Kvp, likely was inappropriately modified by another server, resetting the Kvp.') + SetResourceKvp('pma-voice_enableMicClicks', "true") micClicks = 'true' end sendUIMessage({ @@ -30,13 +30,20 @@ AddEventHandler('onClientResourceStart', function(resource) voiceMode = mode - 1 }) + local radioChannel = LocalPlayer.state.radioChannel or 0 + local callChannel = LocalPlayer.state.callChannel or 0 + -- Reinitialize channels if they're set. - if LocalPlayer.state.radioChannel ~= 0 then - setRadioChannel(LocalPlayer.state.radioChannel) + if radioChannel ~= 0 then + setRadioChannel(radioChannel) end - if LocalPlayer.state.callChannel ~= 0 then - setCallChannel(LocalPlayer.state.callChannel) + if callChannel ~= 0 then + setCallChannel(callChannel) end + if not LocalPlayer.state.disableRadio then + LocalPlayer.state:set("disableRadio", 0, true) + end + print('Script initialization finished.') end) diff --git a/server-data/resources/[phone]/pma-voice/client/init/main.lua b/server-data/resources/[phone]/pma-voice/client/init/main.lua index e643184ee..96faf2d5a 100644 --- a/server-data/resources/[phone]/pma-voice/client/init/main.lua +++ b/server-data/resources/[phone]/pma-voice/client/init/main.lua @@ -3,36 +3,39 @@ local mutedPlayers = {} -- we can't use GetConvarInt because its not a integer, and theres no way to get a float... so use a hacky way it is! local volumes = { -- people are setting this to 1 instead of 1.0 and expecting it to work. - ['radio'] = GetConvarInt('voice_defaultRadioVolume', 30) / 100, - ['phone'] = GetConvarInt('voice_defaultPhoneVolume', 60) / 100, + ['radio'] = GetConvarInt('voice_defaultRadioVolume', 60) / 100, + ['call'] = GetConvarInt('voice_defaultCallVolume', 60) / 100, + ['click_on'] = GetConvarInt('voice_onClickVolume', 10) / 100, + ['click_off'] = GetConvarInt('voice_offClickVolume', 3) / 100, } radioEnabled, radioPressed, mode = true, false, GetConvarInt('voice_defaultVoiceMode', 2) radioData = {} callData = {} - +submixIndicies = {} --- function setVolume --- Toggles the players volume ---@param volume number between 0 and 100 ---@param volumeType string the volume type (currently radio & call) to set the volume of (opt) function setVolume(volume, volumeType) - type_check({volume, "number"}) - local volume = volume / 100 - + type_check({ volume, "number" }) + local volumeFraction = volume / 100 + if volumeType then local volumeTbl = volumes[volumeType] if volumeTbl then LocalPlayer.state:set(volumeType, volume, true) - volumes[volumeType] = volume + volumes[volumeType] = volumeFraction + resyncVolume(volumeType, volumeFraction) else error(('setVolume got a invalid volume type %s'):format(volumeType)) end else - -- _ is here to not mess with global 'type' function - for _type, _ in pairs(volumes) do - volumes[_type] = volume - LocalPlayer.state:set(_type, volume, true) + for volumeType, _ in pairs(volumes) do + volumes[volumeType] = volumeFraction + LocalPlayer.state:set(volumeType, volume, true) end + resyncVolume("all", volumeFraction) end end @@ -40,13 +43,13 @@ exports('setRadioVolume', function(vol) setVolume(vol, 'radio') end) exports('getRadioVolume', function() - return volumes['radio'] + return volumes['radio'] * 100 end) exports("setCallVolume", function(vol) - setVolume(vol, 'phone') + setVolume(vol, 'call') end) exports('getCallVolume', function() - return volumes['phone'] + return volumes['call'] * 100 end) @@ -57,30 +60,71 @@ end) -- rm_mod_freq = 0.0 -- rm_mix = 0.16 -- o_freq_lo = 348.0 --- 0_freq_hi = 4900.0 - -if gameVersion == 'fivem' then - radioEffectId = CreateAudioSubmix('Radio') - SetAudioSubmixEffectRadioFx(radioEffectId, 0) - SetAudioSubmixEffectParamInt(radioEffectId, 0, `default`, 1) - AddAudioSubmixOutput(radioEffectId, 0) - - phoneEffectId = CreateAudioSubmix('Phone') - SetAudioSubmixEffectRadioFx(phoneEffectId, 1) - SetAudioSubmixEffectParamInt(phoneEffectId, 1, `default`, 1) - SetAudioSubmixEffectParamFloat(phoneEffectId, 1, `freq_low`, 300.0) - SetAudioSubmixEffectParamFloat(phoneEffectId, 1, `freq_hi`, 6000.0) - AddAudioSubmixOutput(phoneEffectId, 1) -end +-- o_freq_hi = 4900.0 + +local radioEffectId = CreateAudioSubmix('Radio') +SetAudioSubmixEffectRadioFx(radioEffectId, 0) +-- This is a GetHashKey on purpose, backticks break treesitter in nvim :| +SetAudioSubmixEffectParamInt(radioEffectId, 0, GetHashKey('default'), 1) +SetAudioSubmixOutputVolumes( + radioEffectId, + 0, + 1.0 --[[ frontLeftVolume ]], + 0.25 --[[ frontRightVolume ]], + 0.0 --[[ rearLeftVolume ]], + 0.0 --[[ rearRightVolume ]], + 1.0 --[[ channel5Volume ]], + 1.0 --[[ channel6Volume ]] +) +AddAudioSubmixOutput(radioEffectId, 0) +submixIndicies['radio'] = radioEffectId -local submixFunctions = { - ['radio'] = function(plySource) - MumbleSetSubmixForServerId(plySource, radioEffectId) - end, - ['phone'] = function(plySource) - MumbleSetSubmixForServerId(plySource, phoneEffectId) +local callEffectId = CreateAudioSubmix('Call') +SetAudioSubmixOutputVolumes( + callEffectId, + 1, + 0.10 --[[ frontLeftVolume ]], + 0.50 --[[ frontRightVolume ]], + 0.0 --[[ rearLeftVolume ]], + 0.0 --[[ rearRightVolume ]], + 1.0 --[[ channel5Volume ]], + 1.0 --[[ channel6Volume ]] +) +AddAudioSubmixOutput(callEffectId, 1) +submixIndicies['call'] = callEffectId + +-- Callback is expected to return data in an array, this is for compatibility sake with js, index 0 should be the name and index 1 should be the submixId +-- the callback is sent the effectSlot it can register to, not sure if this is needed, but its here for safety +exports("registerCustomSubmix", function(callback) + local submixTable = callback() + type_check({ submixTable, "table" }) + local submixName, submixId = submixTable[1], submixTable[2] + type_check({ submixName, "string" }, { submixId, "number" }) + logger.info("Creating submix %s with submixId %s", submixName, submixId) + submixIndicies[submixName] = submixId +end) +TriggerEvent("pma-voice:registerCustomSubmixes") + +--- export setEffectSubmix +--- Sets a user defined audio submix for radio and phonecall effects +---@param type string either "call" or "radio" +---@param effectId number submix id returned from CREATE_AUDIO_SUBMIX +exports("setEffectSubmix", function(type, effectId) + type_check({ type, "string" }, { effectId, "number" }) + if submixIndicies[type] then + submixIndicies[type] = effectId end -} +end) + +function restoreDefaultSubmix(plyServerId) + local submix = Player(plyServerId).state.submix + local submixEffect = submixIndicies[submix] + if not submix or not submixEffect then + MumbleSetSubmixForServerId(plyServerId, -1) + return + end + MumbleSetSubmixForServerId(plyServerId, submixEffect) +end -- used to prevent a race condition if they talk again afterwards, which would lead to their voice going to default. local disableSubmixReset = {} @@ -92,23 +136,27 @@ local disableSubmixReset = {} function toggleVoice(plySource, enabled, moduleType) if mutedPlayers[plySource] then return end logger.verbose('[main] Updating %s to talking: %s with submix %s', plySource, enabled, moduleType) - if enabled then + local distance = currentTargets[plySource] + if enabled and (not distance or distance > 4.0) then + print(volumes[moduleType]) MumbleSetVolumeOverrideByServerId(plySource, enabled and volumes[moduleType]) - if GetConvarInt('voice_enableSubmix', 1) == 1 and gameVersion == 'fivem' then + if GetConvarInt('voice_enableSubmix', 1) == 1 then if moduleType then disableSubmixReset[plySource] = true - submixFunctions[moduleType](plySource) + if submixIndicies[moduleType] then + MumbleSetSubmixForServerId(plySource, submixIndicies[moduleType]) + end else - MumbleSetSubmixForServerId(plySource, -1) + restoreDefaultSubmix(plySource) end end - else - if GetConvarInt('voice_enableSubmix', 1) == 1 and gameVersion == 'fivem' then + elseif not enabled then + if GetConvarInt('voice_enableSubmix', 1) == 1 then -- garbage collect it disableSubmixReset[plySource] = nil SetTimeout(250, function() if not disableSubmixReset[plySource] then - MumbleSetSubmixForServerId(plySource, -1) + restoreDefaultSubmix(plySource) end end) end @@ -116,13 +164,34 @@ function toggleVoice(plySource, enabled, moduleType) end end ---- function playerTargets ----Adds players voices to the local players listen channels allowing ----Them to communicate at long range, ignoring proximity range. +local function updateVolumes(voiceTable, override) + for serverId, talking in pairs(voiceTable) do + if serverId == playerServerId then goto skip_iter end + MumbleSetVolumeOverrideByServerId(serverId, talking and override or -1.0) + ::skip_iter:: + end +end + +--- resyncs the call/radio/etc volume to the new volume +---@param volumeType any +function resyncVolume(volumeType, newVolume) + if volumeType == "all" then + resyncVolume("radio", newVolume) + resyncVolume("call", newVolume) + elseif volumeType == "radio" then + updateVolumes(radioData, newVolume) + elseif volumeType == "call" then + updateVolumes(callData, newVolume) + end +end + +---Adds players voices to the local players listen channels allowing them to +---communicate at long range, ignoring proximity range. +--- ---@diagnostic disable-next-line: undefined-doc-param ---@param targets table expects multiple tables to be sent over -function playerTargets(...) - local targets = {...} +function addVoiceTargets(...) + local targets = { ... } local addedPlayers = { [playerServerId] = true } @@ -146,15 +215,26 @@ end --- function playMicClicks ---plays the mic click if the player has them enabled. ----@param clickType boolean whether to play the 'on' or 'off' click. +---@param clickType boolean whether to play the 'on' or 'off' click. function playMicClicks(clickType) if micClicks ~= 'true' then return logger.verbose("Not playing mic clicks because client has them disabled") end + -- TODO: Add customizable radio click volumes sendUIMessage({ sound = (clickType and "audio_on" or "audio_off"), - volume = (clickType and volumes["radio"] or 0.05) + volume = (clickType and volumes['click_on'] or volumes['click_off']) }) end +--- check if player is muted +exports('isPlayerMuted', function(source) + return mutedPlayers[source] +end) + +--- getter for mutedPlayers +exports('getMutedPlayers', function() + return mutedPlayers +end) + --- toggles the targeted player muted ---@param source number the player to mute function toggleMutePlayer(source) @@ -166,6 +246,7 @@ function toggleMutePlayer(source) MumbleSetVolumeOverrideByServerId(source, 0.0) end end + exports('toggleMutePlayer', toggleMutePlayer) --- function setVoiceProperty @@ -175,6 +256,7 @@ exports('toggleMutePlayer', toggleMutePlayer) function setVoiceProperty(type, value) if type == "radioEnabled" then radioEnabled = value + handleRadioEnabledChanged(value) sendUIMessage({ radioEnabled = value }) @@ -184,6 +266,7 @@ function setVoiceProperty(type, value) SetResourceKvp('pma-voice_enableMicClicks', val) end end + exports('setVoiceProperty', setVoiceProperty) -- compatibility exports('SetMumbleProperty', setVoiceProperty) @@ -221,4 +304,22 @@ if gameVersion == 'redm' then Wait(0) end end) -end \ No newline at end of file +end + + +--- handles initializiation for whenever radio or call data changes +--- calls should always be last because they're assumed to always be enabled so +--- theres no delay in talking. +function handleRadioAndCallInit() + for tgt, enabled in pairs(radioData) do + if tgt ~= playerServerId then + toggleVoice(tgt, enabled, 'radio') + end + end + + for tgt, enabled in pairs(callData) do + if tgt ~= playerServerId then + toggleVoice(tgt, true, 'call') + end + end +end diff --git a/server-data/resources/[phone]/pma-voice/client/init/proximity.lua b/server-data/resources/[phone]/pma-voice/client/init/proximity.lua index a3f0789e5..5a627f76a 100644 --- a/server-data/resources/[phone]/pma-voice/client/init/proximity.lua +++ b/server-data/resources/[phone]/pma-voice/client/init/proximity.lua @@ -2,14 +2,16 @@ local disableUpdates = false local isListenerEnabled = false local plyCoords = GetEntityCoords(PlayerPedId()) +proximity = MumbleGetTalkerProximity() +currentTargets = {} function orig_addProximityCheck(ply) local tgtPed = GetPlayerPed(ply) - local voiceModeData = Cfg.voiceModes[mode] - local distance = GetConvar('voice_useNativeAudio', 'false') == 'true' and voiceModeData[1] * 3 or voiceModeData[1] - - return #(plyCoords - GetEntityCoords(tgtPed)) < distance + local voiceRange = GetConvar('voice_useNativeAudio', 'false') == 'true' and proximity * 3 or proximity + local distance = #(plyCoords - GetEntityCoords(tgtPed)) + return distance < voiceRange, distance end + local addProximityCheck = orig_addProximityCheck exports("overrideProximityCheck", function(fn) @@ -24,21 +26,35 @@ function addNearbyPlayers() if disableUpdates then return end -- update here so we don't have to update every call of addProximityCheck plyCoords = GetEntityCoords(PlayerPedId()) - + proximity = MumbleGetTalkerProximity() + currentTargets = {} MumbleClearVoiceTargetChannels(voiceTarget) + if LocalPlayer.state.disableProximity then return end + MumbleAddVoiceChannelListen(LocalPlayer.state.assignedChannel) + MumbleAddVoiceTargetChannel(voiceTarget, LocalPlayer.state.assignedChannel) + + for source, _ in pairs(callData) do + if source ~= playerServerId then + MumbleAddVoiceTargetChannel(voiceTarget, MumbleGetVoiceChannelFromServerId(source)) + end + end + + local players = GetActivePlayers() for i = 1, #players do local ply = players[i] local serverId = GetPlayerServerId(ply) - - if addProximityCheck(ply) then - if isTarget then goto skip_loop end - - logger.verbose('Added %s as a voice target', serverId) - MumbleAddVoiceTargetChannel(voiceTarget, serverId) + local shouldAdd, distance = addProximityCheck(ply) + if shouldAdd then + -- if distance then + -- currentTargets[serverId] = distance + -- else + -- -- backwards compat, maybe remove in v7 + -- currentTargets[serverId] = 15.0 + -- end + -- logger.verbose('Added %s as a voice target', serverId) + MumbleAddVoiceTargetChannel(voiceTarget, MumbleGetVoiceChannelFromServerId(serverId)) end - - ::skip_loop:: end end @@ -52,7 +68,7 @@ function setSpectatorMode(enabled) local serverId = GetPlayerServerId(ply) if serverId == playerServerId then goto skip_loop end logger.verbose("Adding %s to listen table", serverId) - MumbleAddVoiceChannelListen(serverId) + MumbleAddVoiceChannelListen(MumbleGetVoiceChannelFromServerId(serverId)) ::skip_loop:: end else @@ -61,7 +77,7 @@ function setSpectatorMode(enabled) local serverId = GetPlayerServerId(ply) if serverId == playerServerId then goto skip_loop end logger.verbose("Removing %s from listen table", serverId) - MumbleRemoveVoiceChannelListen(serverId) + MumbleRemoveVoiceChannelListen(MumbleGetVoiceChannelFromServerId(serverId)) ::skip_loop:: end end @@ -69,33 +85,39 @@ end RegisterNetEvent('onPlayerJoining', function(serverId) if isListenerEnabled then - MumbleAddVoiceChannelListen(serverId) + MumbleAddVoiceChannelListen(MumbleGetVoiceChannelFromServerId(serverId)) logger.verbose("Adding %s to listen table", serverId) end end) RegisterNetEvent('onPlayerDropped', function(serverId) if isListenerEnabled then - MumbleRemoveVoiceChannelListen(serverId) + MumbleRemoveVoiceChannelListen(MumbleGetVoiceChannelFromServerId(serverId)) logger.verbose("Removing %s from listen table", serverId) end end) +local listenerOverride = false +exports("setListenerOverride", function(enabled) + type_check({ enabled, "boolean" }) + listenerOverride = enabled +end) + -- cache talking status so we only send a nui message when its not the same as what it was before local lastTalkingStatus = false local lastRadioStatus = false local voiceState = "proximity" -Citizen.CreateThread(function() +CreateThread(function() TriggerEvent('chat:addSuggestion', '/muteply', 'Mutes the player with the specified id', { { name = "player id", help = "the player to toggle mute" }, - { name = "duration", help = "(opt) the duration the mute in seconds (default: 900)" } + { name = "duration", help = "(opt) the duration the mute in seconds (default: 900)" } }) while true do -- wait for mumble to reconnect while not MumbleIsConnected() do Wait(100) end - -- Leave the check here as we don't want to do any of this logic + -- Leave the check here as we don't want to do any of this logic if GetConvarInt('voice_enableUi', 1) == 1 then local curTalkingStatus = MumbleIsPlayerTalking(PlayerId()) == 1 if lastRadioStatus ~= radioPressed or lastTalkingStatus ~= curTalkingStatus then @@ -110,10 +132,12 @@ Citizen.CreateThread(function() if voiceState == "proximity" then addNearbyPlayers() - local isSpectating = NetworkIsInSpectatorMode() - if isSpectating and not isListenerEnabled then + -- What a name, wowza + local cam = GetConvarInt("voice_disableAutomaticListenerOnCamera", 0) ~= 1 and GetRenderingCam() or -1 + local isSpectating = NetworkIsInSpectatorMode() or cam ~= -1 + if not isListenerEnabled and (isSpectating or listenerOverride) then setSpectatorMode(true) - elseif not isSpectating and isListenerEnabled then + elseif isListenerEnabled and not isSpectating and not listenerOverride then setSpectatorMode(false) end end @@ -128,7 +152,7 @@ exports("setVoiceState", function(_voiceState, channel) end voiceState = _voiceState if voiceState == "channel" then - type_check({channel, "number"}) + type_check({ channel, "number" }) -- 65535 is the highest a client id can go, so we add that to the base channel so we don't manage to get onto a players channel channel = channel + 65535 MumbleSetVoiceChannel(channel) @@ -149,8 +173,40 @@ AddEventHandler("onClientResourceStop", function(resource) local isResource = string.match(proximityCheckRef, resource) if isResource then addProximityCheck = orig_addProximityCheck - logger.warn('Reset proximity check to default, the original resource [%s] which provided the function restarted', resource) + logger.warn( + 'Reset proximity check to default, the original resource [%s] which provided the function restarted', + resource) + end + end + end +end) + +exports("addVoiceMode", function(distance, name) + for i = 1, #Cfg.voiceModes do + local voiceMode = Cfg.voiceModes[i] + if voiceMode[2] == name then + logger.verbose("Already had %s, overwritting instead", name) + voiceMode[1] = distance + return + end + end + Cfg.voiceModes[#Cfg.voiceModes + 1] = { distance, name } +end) + +exports("removeVoiceMode", function(name) + for i = 1, #Cfg.voiceModes do + local voiceMode = Cfg.voiceModes[i] + if voiceMode[2] == name then + table.remove(Cfg.voiceModes, i) + -- Reset our current range if we had it + if mode == i then + local newMode = Cfg.voiceModes[1] + mode = 1 + setProximityState(newMode[mode], false) end + return true end end -end) \ No newline at end of file + + return false +end) diff --git a/server-data/resources/[phone]/pma-voice/client/init/submix.lua b/server-data/resources/[phone]/pma-voice/client/init/submix.lua new file mode 100644 index 000000000..a8235d422 --- /dev/null +++ b/server-data/resources/[phone]/pma-voice/client/init/submix.lua @@ -0,0 +1,19 @@ +AddStateBagChangeHandler("submix", "", function(bagName, _, value) + local tgtId = tonumber(bagName:gsub('player:', ''), 10) + if not tgtId then return end + -- We got an invalid submix, discard we don't care about it + if value and not submixIndicies[value] then + return logger.warn("Player %s applied submix %s but it isn't valid", + tgtId, value) + end + -- we don't want to reset submix if the player is talking on the radio + if not value then + if not radioData[tgtId] and not callData[tgtId] then + logger.info("Resetting submix for player %s", tgtId) + MumbleSetSubmixForServerId(tgtId, -1) + end + return + end + logger.info("%s had their submix set to %s", tgtId, value) + MumbleSetSubmixForServerId(tgtId, submixIndicies[value]) +end) diff --git a/server-data/resources/[phone]/pma-voice/client/module/phone.lua b/server-data/resources/[phone]/pma-voice/client/module/phone.lua index 35a3b5218..d768251aa 100644 --- a/server-data/resources/[phone]/pma-voice/client/module/phone.lua +++ b/server-data/resources/[phone]/pma-voice/client/module/phone.lua @@ -1,74 +1,44 @@ local callChannel = 0 ----function createPhoneThread ----creates a phone thread to listen for key presses -local function createPhoneThread() - Citizen.CreateThread(function() - local changed = false - while callChannel ~= 0 do - -- check if they're pressing voice keybinds - if MumbleIsPlayerTalking(PlayerId()) and not changed then - changed = true - playerTargets(radioPressed and radioData or {}, callData) - TriggerServerEvent('pma-voice:setTalkingOnCall', true) - elseif changed and MumbleIsPlayerTalking(PlayerId()) ~= 1 then - changed = false - MumbleClearVoiceTargetPlayers(voiceTarget) - TriggerServerEvent('pma-voice:setTalkingOnCall', false) - end - Wait(0) - end - end) -end - RegisterNetEvent('pma-voice:syncCallData', function(callTable, channel) callData = callTable - for tgt, enabled in pairs(callTable) do - if tgt ~= playerServerId then - toggleVoice(tgt, enabled, 'phone') - end - end -end) - -RegisterNetEvent('pma-voice:setTalkingOnCall', function(tgt, enabled) - if tgt ~= playerServerId then - callData[tgt] = enabled - toggleVoice(tgt, enabled, 'phone') - end + handleRadioAndCallInit() end) RegisterNetEvent('pma-voice:addPlayerToCall', function(plySource) - callData[plySource] = false + toggleVoice(plySource, true, 'call') + callData[plySource] = true end) RegisterNetEvent('pma-voice:removePlayerFromCall', function(plySource) if plySource == playerServerId then for tgt, _ in pairs(callData) do if tgt ~= playerServerId then - toggleVoice(tgt, false, 'phone') + toggleVoice(tgt, false, 'call') end end callData = {} MumbleClearVoiceTargetPlayers(voiceTarget) - playerTargets(radioPressed and radioData or {}, callData) + addVoiceTargets(radioPressed and radioData or {}, callData) else callData[plySource] = nil - toggleVoice(plySource, false, 'phone') + if not radioData[plySource] then + toggleVoice(plySource, false, 'call') + end if MumbleIsPlayerTalking(PlayerId()) then MumbleClearVoiceTargetPlayers(voiceTarget) - playerTargets(radioPressed and radioData or {}, callData) + addVoiceTargets(radioPressed and radioData or {}, callData) end end end) function setCallChannel(channel) - if GetConvarInt('voice_enablePhones', 1) ~= 1 then return end + if GetConvarInt('voice_enableCalls', 1) ~= 1 then return end TriggerServerEvent('pma-voice:setPlayerCall', channel) callChannel = channel sendUIMessage({ callInfo = channel }) - createPhoneThread() end exports('setCallChannel', setCallChannel) @@ -85,7 +55,6 @@ exports('removePlayerFromCall', function() end) RegisterNetEvent('pma-voice:clSetPlayerCall', function(_callChannel) - if GetConvarInt('voice_enablePhones', 1) ~= 1 then return end + if GetConvarInt('voice_enableCalls', 1) ~= 1 then return end callChannel = _callChannel - createPhoneThread() end) diff --git a/server-data/resources/[phone]/pma-voice/client/module/radio.lua b/server-data/resources/[phone]/pma-voice/client/module/radio.lua index 583a96d3e..6d2f911cd 100644 --- a/server-data/resources/[phone]/pma-voice/client/module/radio.lua +++ b/server-data/resources/[phone]/pma-voice/client/module/radio.lua @@ -14,19 +14,21 @@ function syncRadioData(radioTable, localPlyRadioName) tPrint(radioData) print('-----------------------------') end - for tgt, enabled in pairs(radioTable) do - if tgt ~= playerServerId then - toggleVoice(tgt, enabled, 'radio') - end + + local isRadioEnabled = radioEnabled and LocalPlayer.state.disableRadio == 0 + if isRadioEnabled then + handleRadioAndCallInit() end + sendUIMessage({ radioChannel = radioChannel, - radioEnabled = radioEnabled + radioEnabled = isRadioEnabled }) if GetConvarInt("voice_syncPlayerNames", 0) == 1 then radioNames[playerServerId] = localPlyRadioName end end + RegisterNetEvent('pma-voice:syncRadioData', syncRadioData) --- event setTalkingOnRadio @@ -34,10 +36,16 @@ RegisterNetEvent('pma-voice:syncRadioData', syncRadioData) ---@param plySource number the players server id. ---@param enabled boolean whether the player is talking or not. function setTalkingOnRadio(plySource, enabled) - toggleVoice(plySource, enabled, 'radio') radioData[plySource] = enabled + -- if we don't have radioEnabled don't actually set them as talking (we still want the state to enable people talking later) + if not radioEnabled or LocalPlayer.state.disableRadio ~= 0 then return logger.info("[radio] Ignoring setTalkingOnRadio. radioEnabled: %s disableRadio: %s", radioEnabled, LocalPlayer.state.disableRadio) end + -- If we're on a call we don't want to toggle their voice disabled this will break calls. + if not callData[plySource] then + toggleVoice(plySource, enabled, 'radio') + end playMicClicks(enabled) end + RegisterNetEvent('pma-voice:setTalkingOnRadio', setTalkingOnRadio) --- event addPlayerToRadio @@ -48,13 +56,13 @@ function addPlayerToRadio(plySource, plyRadioName) if GetConvarInt("voice_syncPlayerNames", 0) == 1 then radioNames[plySource] = plyRadioName end + logger.info('[radio] %s joined radio %s %s', plySource, radioChannel, + radioPressed and " while we were talking, adding them to targets" or "") if radioPressed then - logger.info('[radio] %s joined radio %s while we were talking, adding them to targets', plySource, radioChannel) - playerTargets(radioData, MumbleIsPlayerTalking(PlayerId()) and callData or {}) - else - logger.info('[radio] %s joined radio %s', plySource, radioChannel) + addVoiceTargets(radioData, callData) end end + RegisterNetEvent('pma-voice:addPlayerToRadio', addPlayerToRadio) --- event removePlayerFromRadio @@ -74,12 +82,12 @@ function removePlayerFromRadio(plySource) }) radioNames = {} radioData = {} - playerTargets(MumbleIsPlayerTalking(PlayerId()) and callData or {}) + addVoiceTargets(callData) else - toggleVoice(plySource, false) + toggleVoice(plySource, false, 'radio') if radioPressed then logger.info('[radio] %s left radio %s while we were talking, updating targets.', plySource, radioChannel) - playerTargets(radioData, MumbleIsPlayerTalking(PlayerId()) and callData or {}) + addVoiceTargets(radioData, callData) else logger.info('[radio] %s has left radio %s', plySource, radioChannel) end @@ -89,21 +97,26 @@ function removePlayerFromRadio(plySource) end end end + RegisterNetEvent('pma-voice:removePlayerFromRadio', removePlayerFromRadio) +RegisterNetEvent('pma-voice:radioChangeRejected', function() + logger.info("The server rejected your radio change.") + radioChannel = 0 +end) + --- function setRadioChannel --- sets the local players current radio channel and updates the server ---@param channel number the channel to set the player to, or 0 to remove them. function setRadioChannel(channel) if GetConvarInt('voice_enableRadios', 1) ~= 1 then return end - type_check({channel, "number"}) + type_check({ channel, "number" }) TriggerServerEvent('pma-voice:setPlayerRadio', channel) radioChannel = channel end --- exports setRadioChannel --- sets the local players current radio channel and updates the server ----@param channel number the channel to set the player to, or 0 to remove them. exports('setRadioChannel', setRadioChannel) -- mumble-voip compatability exports('SetRadioChannel', setRadioChannel) @@ -131,10 +144,14 @@ exports('toggleRadioAnim', function() TriggerEvent('pma-voice:toggleRadioAnim', disableRadioAnim) end) +exports("setDisableRadioAnim", function(shouldDisable) + disableRadioAnim = shouldDisable +end) + -- exports disableRadioAnim --- returns whether the client is undercover or not exports('getRadioAnimState', function() - return toggleRadioAnim + return disableRadioAnim end) --- check if the player is dead @@ -148,49 +165,80 @@ function isDead() elseif IsPlayerDead(PlayerId()) then return true end + return false +end + +function isRadioAnimEnabled() + if + GetConvarInt('voice_enableRadioAnim', 1) == 1 + and not (GetConvarInt('voice_disableVehicleRadioAnim', 0) == 1 + and IsPedInAnyVehicle(PlayerPedId(), false)) + and not disableRadioAnim then + return true + end + return false end RegisterCommand('+radiotalk', function() if GetConvarInt('voice_enableRadios', 1) ~= 1 then return end - if isDead() then return end - + if isDead() or LocalPlayer.state.disableRadio ~= 0 then return end if not radioPressed and radioEnabled then if radioChannel > 0 then logger.info('[radio] Start broadcasting, update targets and notify server.') - playerTargets(radioData, MumbleIsPlayerTalking(PlayerId()) and callData or {}) + addVoiceTargets(radioData, callData) TriggerServerEvent('pma-voice:setTalkingOnRadio', true) radioPressed = true + local shouldPlayAnimation = isRadioAnimEnabled() playMicClicks(true) - if GetConvarInt('voice_enableRadioAnim', 0) == 1 and not (GetConvarInt('voice_disableVehicleRadioAnim', 0) == 1 and IsPedInAnyVehicle(PlayerPedId(), false)) then - if not disableRadioAnim then - RequestAnimDict('random@arrests') - while not HasAnimDictLoaded('random@arrests') do - Citizen.Wait(10) - end - TaskPlayAnim(PlayerPedId(), "random@arrests", "generic_radio_enter", 8.0, 2.0, -1, 50, 2.0, 0, 0, 0) - end + if shouldPlayAnimation then + RequestAnimDict('random@arrests') end - Citizen.CreateThread(function() + CreateThread(function() TriggerEvent("pma-voice:radioActive", true) + LocalPlayer.state:set("radioActive", true, true); + local checkFailed = false while radioPressed do - Wait(0) + if radioChannel < 0 or not radioEnabled or isDead() or LocalPlayer.state.disableRadio ~= 0 then + checkFailed = true + break + end + if shouldPlayAnimation and HasAnimDictLoaded("random@arrests") then + if not IsEntityPlayingAnim(PlayerPedId(), "random@arrests", "generic_radio_enter", 3) then + TaskPlayAnim(PlayerPedId(), "random@arrests", "generic_radio_enter", 8.0, 2.0, -1, 50, 2.0, false, + false, + false) + end + end SetControlNormal(0, 249, 1.0) SetControlNormal(1, 249, 1.0) SetControlNormal(2, 249, 1.0) + Wait(0) + end + + + if checkFailed then + logger.info("Canceling radio talking as the checks have failed.") + ExecuteCommand("-radiotalk") + end + if shouldPlayAnimation then + RemoveAnimDict('random@arrests') end end) + else + logger.info("Player tried to talk but was not on a radio channel") end end end, false) RegisterCommand('-radiotalk', function() - if radioChannel > 0 or radioEnabled and radioPressed then + if (radioChannel > 0 or radioEnabled) and radioPressed then radioPressed = false MumbleClearVoiceTargetPlayers(voiceTarget) - playerTargets(MumbleIsPlayerTalking(PlayerId()) and callData or {}) + addVoiceTargets(callData) TriggerEvent("pma-voice:radioActive", false) + LocalPlayer.state:set("radioActive", false, true); playMicClicks(false) - if GetConvarInt('voice_enableRadioAnim', 0) == 1 then + if GetConvarInt('voice_enableRadioAnim', 1) == 1 then StopAnimTask(PlayerPedId(), "random@arrests", "generic_radio_enter", -4.0) end TriggerServerEvent('pma-voice:setTalkingOnRadio', false) @@ -208,4 +256,35 @@ function syncRadio(_radioChannel) logger.info('[radio] radio set serverside update to radio %s', radioChannel) radioChannel = _radioChannel end + RegisterNetEvent('pma-voice:clSetPlayerRadio', syncRadio) + + +--- handles "radioEnabled" changing +---@param wasRadioEnabled boolean whether radio is enabled or not +function handleRadioEnabledChanged(wasRadioEnabled) + if wasRadioEnabled then + syncRadioData(radioData, "") + else + removePlayerFromRadio(playerServerId) + end +end + +--- adds the bit to the disableRadio bits +---@param bit number the bit to add +local function addRadioDisableBit(bit) + local curVal = LocalPlayer.state.disableRadio or 0 + curVal = curVal | bit + LocalPlayer.state:set("disableRadio", curVal, true) +end +exports("addRadioDisableBit", addRadioDisableBit) + +--- removes the bit from disableRadio +---@param bit number the bit to remove +local function removeRadioDisableBit(bit) + local curVal = LocalPlayer.state.disableRadio or 0 + curVal = curVal & (~bit) + LocalPlayer.state:set("disableRadio", curVal, true) +end +exports("removeRadioDisableBit", removeRadioDisableBit) + diff --git a/server-data/resources/[phone]/pma-voice/client/utils/Nui.lua b/server-data/resources/[phone]/pma-voice/client/utils/Nui.lua index 871e91a58..dd9e914fa 100644 --- a/server-data/resources/[phone]/pma-voice/client/utils/Nui.lua +++ b/server-data/resources/[phone]/pma-voice/client/utils/Nui.lua @@ -8,4 +8,4 @@ RegisterNUICallback("uiReady", function(data, cb) uiReady:resolve(true) cb('ok') -end) \ No newline at end of file +end) diff --git a/server-data/resources/[phone]/pma-voice/docs/client-setters/setRadioChannel.md b/server-data/resources/[phone]/pma-voice/docs/client-setters/setRadioChannel.md index 0ef8b9f38..ee0ea376f 100644 --- a/server-data/resources/[phone]/pma-voice/docs/client-setters/setRadioChannel.md +++ b/server-data/resources/[phone]/pma-voice/docs/client-setters/setRadioChannel.md @@ -15,7 +15,7 @@ Sets the local players radio channel. exports['pma-voice']:setRadioChannel(1) -- This will remove the player from all radio channels -expots ['pma-voice']:setRadioChannel(0) +exports ['pma-voice']:setRadioChannel(0) ``` addPlayerToRadio is provided as a 'easier to read' alternative to setRadioChannel. @@ -23,4 +23,4 @@ addPlayerToRadio is provided as a 'easier to read' alternative to setRadioChanne ```lua -- Joins radio channel 1 exports['pma-voice']:addPlayerToRadio(1) -``` \ No newline at end of file +``` diff --git a/server-data/resources/[phone]/pma-voice/fxmanifest.lua b/server-data/resources/[phone]/pma-voice/fxmanifest.lua index 7f3ec5889..4a4d73abe 100644 --- a/server-data/resources/[phone]/pma-voice/fxmanifest.lua +++ b/server-data/resources/[phone]/pma-voice/fxmanifest.lua @@ -5,7 +5,7 @@ author 'AvarianKnight' description 'VOIP built using FiveM\'s built in mumble.' dependencies { - '/onesync', + '/onesync', } lua54 'yes' @@ -17,53 +17,54 @@ client_scripts { 'client/init/proximity.lua', 'client/init/init.lua', 'client/init/main.lua', + 'client/init/submix.lua', 'client/module/*.lua', - 'client/*.lua', + 'client/*.lua', } server_scripts { - 'server/**/*.lua', + 'server/**/*.lua', 'server/**/*.js' } files { - 'ui/*.ogg', - 'ui/css/*.css', - 'ui/js/*.js', - 'ui/index.html', + 'ui/*.ogg', + 'ui/css/*.css', + 'ui/js/*.js', + 'ui/index.html', } ui_page 'ui/index.html' provides { 'mumble-voip', - 'tokovoip', - 'toko-voip', - 'tokovoip_script' + 'tokovoip', + 'toko-voip', + 'tokovoip_script' } convar_category 'PMA-Voice' { - "PMA-Voice Configuration Options", - { - { "Use native audio", "$voice_useNativeAudio", "CV_BOOL", "false" }, - { "Use 2D audio", "$voice_use2dAudio", "CV_BOOL", "false" }, - { "Use sending range only", "$voice_useSendingRangeOnly", "CV_BOOL", "false" }, - { "Enable UI", "$voice_enableUi", "CV_INT", "1" }, - { "Enable F11 proximity key", "$voice_enableProximityCycle", "CV_INT", "1" }, - { "Proximity cycle key", "$voice_defaultCycle", "CV_STRING", "F11" }, - { "Voice radio volume", "$voice_defaultRadioVolume", "CV_INT", "30" }, - { "Voice phone volume", "$voice_defaultPhoneVolume", "CV_INT", "60" }, - { "Enable radios", "$voice_enableRadios", "CV_INT", "1" }, - { "Enable phones", "$voice_enablePhones", "CV_INT", "1" }, - { "Enable submix", "$voice_enableSubmix", "CV_INT", "1" }, - { "Enable radio animation", "$voice_enableRadioAnim", "CV_INT", "0" }, - { "Radio key", "$voice_defaultRadio", "CV_STRING", "LALT" }, - { "UI refresh rate", "$voice_uiRefreshRate", "CV_INT", "200" }, - { "Allow players to set audio intent", "$voice_allowSetIntent", "CV_INT", "1" }, - { "External mumble server address", "$voice_externalAddress", "CV_STRING", "" }, - { "External mumble server port", "$voice_externalPort", "CV_INT", "0" }, - { "Voice debug mode", "$voice_debugMode", "CV_INT", "0" }, - { "Disable players being allowed to join", "$voice_externalDisallowJoin", "CV_INT", "0" }, - { "Hide server endpoints in logs", "$voice_hideEndpoints", "CV_INT", "1" }, - } + "PMA-Voice Configuration Options", + { + { "Use native audio", "$voice_useNativeAudio", "CV_BOOL", "false" }, + { "Use 2D audio", "$voice_use2dAudio", "CV_BOOL", "false" }, + { "Use sending range only", "$voice_useSendingRangeOnly", "CV_BOOL", "false" }, + { "Enable UI", "$voice_enableUi", "CV_INT", "1" }, + { "Enable F11 proximity key", "$voice_enableProximityCycle", "CV_INT", "1" }, + { "Proximity cycle key", "$voice_defaultCycle", "CV_STRING", "F11" }, + { "Voice radio volume", "$voice_defaultRadioVolume", "CV_INT", "30" }, + { "Voice call volume", "$voice_defaultCallVolume", "CV_INT", "60" }, + { "Enable radios", "$voice_enableRadios", "CV_INT", "1" }, + { "Enable calls", "$voice_enableCalls", "CV_INT", "1" }, + { "Enable submix", "$voice_enableSubmix", "CV_INT", "1" }, + { "Enable radio animation", "$voice_enableRadioAnim", "CV_INT", "0" }, + { "Radio key", "$voice_defaultRadio", "CV_STRING", "LMENU" }, + { "UI refresh rate", "$voice_uiRefreshRate", "CV_INT", "200" }, + { "Allow players to set audio intent", "$voice_allowSetIntent", "CV_INT", "1" }, + { "External mumble server address", "$voice_externalAddress", "CV_STRING", "" }, + { "External mumble server port", "$voice_externalPort", "CV_INT", "0" }, + { "Voice debug mode", "$voice_debugMode", "CV_INT", "0" }, + { "Disable players being allowed to join", "$voice_externalDisallowJoin", "CV_INT", "0" }, + { "Hide server endpoints in logs", "$voice_hideEndpoints", "CV_INT", "1" }, + } } diff --git a/server-data/resources/[phone]/pma-voice/server/main.lua b/server-data/resources/[phone]/pma-voice/server/main.lua index 7b76d64d3..eed2420ae 100644 --- a/server-data/resources/[phone]/pma-voice/server/main.lua +++ b/server-data/resources/[phone]/pma-voice/server/main.lua @@ -2,6 +2,17 @@ voiceData = {} radioData = {} callData = {} +local mappedChannels = {} +function firstFreeChannel() + for i = 1, 2048 do + if not mappedChannels[i] then + return i + end + end + + return 0 +end + function defaultTable(source) handleStateBagInitilization(source) return { @@ -14,9 +25,10 @@ end function handleStateBagInitilization(source) local plyState = Player(source).state - if not plyState.pmaVoiceInit then + if not plyState.pmaVoiceInit then plyState:set('radio', GetConvarInt('voice_defaultRadioVolume', 30), true) - plyState:set('phone', GetConvarInt('voice_defaultPhoneVolume', 60), true) + plyState:set('call', GetConvarInt('voice_defaultCallVolume', 60), true) + plyState:set('submix', nil, true) plyState:set('proximity', {}, true) plyState:set('callChannel', 0, true) plyState:set('radioChannel', 0, true) @@ -24,10 +36,18 @@ function handleStateBagInitilization(source) -- 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 assignedChannel = firstFreeChannel() + plyState:set('assignedChannel', assignedChannel, true) + if assignedChannel ~= 0 then + mappedChannels[assignedChannel] = source + logger.verbose('[reuse] Assigned %s to channel %s', source, assignedChannel) + else + logger.error('[reuse] Failed to find a free channel for %s', source) + end +end +CreateThread(function() local plyTbl = GetPlayers() for i = 1, #plyTbl do local ply = tonumber(plyTbl[i]) @@ -48,44 +68,31 @@ Citizen.CreateThread(function() 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\'') + SetConvarReplicated('voice_useNativeAudio', 'true') + if sendingRangeOnly == 'false' then + SetConvarReplicated('voice_useSendingRangeOnly', 'true') + logger.info( + 'No convars detected for voice mode, defaulting to \'setr voice_useNativeAudio 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 + 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 local radioVolume = GetConvarInt("voice_defaultRadioVolume", 30) - local phoneVolume = GetConvarInt("voice_defaultPhoneVolume", 60) + local callVolume = GetConvarInt("voice_defaultCallVolume", 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 + callVolume == 0 or callVolume == 1 then SetConvarReplicated("voice_defaultRadioVolume", 30) - SetConvarReplicated("voice_defaultPhoneVolume", 60) + SetConvarReplicated("voice_defaultCallVolume", 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.") + logger.warn( + "`voice_defaultRadioVolume` or `voice_defaultCallVolume` have their value set as a float, this is going to automatically be fixed but please update your convars.") end end end) @@ -98,6 +105,8 @@ end) AddEventHandler("playerDropped", function() local source = source + local mappedChannel = Player(source).state.assignedChannel + if voiceData[source] then local plyData = voiceData[source] @@ -111,6 +120,11 @@ AddEventHandler("playerDropped", function() voiceData[source] = nil end + + if mappedChannel then + mappedChannels[mappedChannel] = nil + logger.verbose('[reuse] Unassigned %s from channel %s', source, mappedChannel) + end end) if GetConvarInt('voice_externalDisallowJoin', 0) == 1 then @@ -125,6 +139,7 @@ end function isValidPlayer(source) return voiceData[source] end + exports('isValidPlayer', isValidPlayer) function getPlayersInRadioChannel(channel) @@ -135,5 +150,6 @@ function getPlayersInRadioChannel(channel) -- channel doesnt exist return {} end + exports('getPlayersInRadioChannel', getPlayersInRadioChannel) -exports('GetPlayersInRadioChannel', getPlayersInRadioChannel) \ No newline at end of file +exports('GetPlayersInRadioChannel', getPlayersInRadioChannel) diff --git a/server-data/resources/[phone]/pma-voice/server/module/phone.lua b/server-data/resources/[phone]/pma-voice/server/module/phone.lua index bb6b73739..ac6412137 100644 --- a/server-data/resources/[phone]/pma-voice/server/module/phone.lua +++ b/server-data/resources/[phone]/pma-voice/server/module/phone.lua @@ -2,52 +2,53 @@ ---@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) + logger.verbose('[call] 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 + 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 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]) + logger.verbose('[call] 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] = true + 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 + if GetConvarInt('voice_enableCalls', 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)) + return logger.warn("%s sent a invalid call, 'callChannel' expected 'number', got: %s", source, + type(_callChannel)) end end if isResource then @@ -56,39 +57,20 @@ function setPlayerCall(source, _callChannel) TriggerClientEvent('pma-voice:clSetPlayerCall', source, callChannel) end - Player(source).state.callChannel = callChannel + 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 + 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) + 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) \ No newline at end of file diff --git a/server-data/resources/[phone]/pma-voice/server/module/radio.lua b/server-data/resources/[phone]/pma-voice/server/module/radio.lua index ca8792029..576c170cb 100644 --- a/server-data/resources/[phone]/pma-voice/server/module/radio.lua +++ b/server-data/resources/[phone]/pma-voice/server/module/radio.lua @@ -26,6 +26,7 @@ function addChannelCheck(channel, cb) radioChecks[channel] = cb logger.info("%s added a check to channel %s", GetInvokingResource(), channel) end + exports('addChannelCheck', addChannelCheck) local function radioNameGetter_orig(source) @@ -43,15 +44,19 @@ function overrideRadioNameGetter(channel, cb) 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 +---@return boolean wasAdded if the player was successfuly added to the radio channel, or if it failed. 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) + TriggerClientEvent("pma-voice:radioChangeRejected", source) + TriggerClientEvent('pma-voice:removePlayerFromRadio', source, source) + return false end logger.verbose('[radio] Added %s to radio %s', source, radioChannel) @@ -65,7 +70,9 @@ function addPlayerToRadio(source, radioChannel) 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) + TriggerClientEvent('pma-voice:syncRadioData', source, radioData[radioChannel], + GetConvarInt("voice_syncPlayerNames", 0) == 1 and plyName) + return true end --- removes a player from the specified channel @@ -95,9 +102,10 @@ function setPlayerRadio(source, _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))) + 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)) + return logger.warn("%s sent a invalid radio, 'radioChannel' expected 'number', got: %s", source, + type(_radioChannel)) end end if isResource then @@ -105,16 +113,18 @@ function setPlayerRadio(source, _radioChannel) -- changed TriggerClientEvent('pma-voice:clSetPlayerRadio', source, radioChannel) end - Player(source).state.radioChannel = radioChannel - if radioChannel ~= 0 and plyVoice.radio == 0 then - addPlayerToRadio(source, radioChannel) + if radioChannel ~= 0 then + if plyVoice.radio > 0 then + removePlayerFromRadio(source, plyVoice.radio) + end + local wasAdded = addPlayerToRadio(source, radioChannel) + Player(source).state.radioChannel = wasAdded and radioChannel or 0 elseif radioChannel == 0 then removePlayerFromRadio(source, plyVoice.radio) - elseif plyVoice.radio > 0 then - removePlayerFromRadio(source, plyVoice.radio) - addPlayerToRadio(source, radioChannel) + Player(source).state.radioChannel = 0 end end + exports('setPlayerRadio', setPlayerRadio) RegisterNetEvent('pma-voice:setPlayerRadio', function(radioChannel) @@ -130,15 +140,17 @@ function setTalkingOnRadio(talking) 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) + 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') + 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) @@ -147,19 +159,20 @@ AddEventHandler("onResourceStop", function(resource) 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) + 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) + local isResource = string.match(radioRef, 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') + logger.warn( + 'Radio name getter is resetting to default because the resource that gave the cb got turned off') end end end - -end) \ No newline at end of file +end) diff --git a/server-data/resources/[phone]/pma-voice/server/mute.js b/server-data/resources/[phone]/pma-voice/server/mute.js index 536b61e1c..61b45ffed 100644 --- a/server-data/resources/[phone]/pma-voice/server/mute.js +++ b/server-data/resources/[phone]/pma-voice/server/mute.js @@ -4,7 +4,7 @@ let mutedPlayers = {} RegisterCommand('muteply', (source, args) => { const mutePly = parseInt(args[0]) const duration = parseInt(args[1]) || 900 - if (mutePly && exports['pma-voice'].isValidPlayer(mutePly)) { + if (mutePly && exports[GetCurrentResourceName()].isValidPlayer(mutePly)) { const isMuted = !MumbleIsPlayerMuted(mutePly); Player(mutePly).state.muted = isMuted; MumbleSetPlayerMuted(mutePly, isMuted); diff --git a/server-data/resources/[phone]/pma-voice/shared.lua b/server-data/resources/[phone]/pma-voice/shared.lua index 1867d844a..a81d5d78d 100644 --- a/server-data/resources/[phone]/pma-voice/shared.lua +++ b/server-data/resources/[phone]/pma-voice/shared.lua @@ -8,6 +8,38 @@ gameVersion = GetGameName() if not IsDuplicityVersion() then LocalPlayer = LocalPlayer playerServerId = GetPlayerServerId(PlayerId()) + + if gameVersion == "redm" then + function CreateAudioSubmix(name) + return Citizen.InvokeNative(0x658d2bc8, name, Citizen.ResultAsInteger()) + end + + function AddAudioSubmixOutput(submixId, outputSubmixId) + Citizen.InvokeNative(0xAC6E290D, submixId, outputSubmixId) + end + + function MumbleSetSubmixForServerId(serverId, submixId) + Citizen.InvokeNative(0xFE3A3054, serverId, submixId) + end + + function SetAudioSubmixEffectParamFloat(submixId, effectSlot, paramIndex, paramValue) + Citizen.InvokeNative(0x9A209B3C, submixId, effectSlot, paramIndex, paramValue) + end + + function SetAudioSubmixEffectParamInt(submixId, effectSlot, paramIndex, paramValue) + Citizen.InvokeNative(0x77FAE2B8, submixId, effectSlot, paramIndex, paramValue) + end + + function SetAudioSubmixEffectRadioFx(submixId, effectSlot) + Citizen.InvokeNative(0xAAA94D53, submixId, effectSlot) + end + + function SetAudioSubmixOutputVolumes(submixId, outputSlot, frontLeftVolume, frontRightVolume, rearLeftVolume, + rearRightVolume, channel5Volume, channel6Volume) + Citizen.InvokeNative(0x825DC0D1, submixId, outputSlot, frontLeftVolume, frontRightVolume, rearLeftVolume, + rearRightVolume, channel5Volume, channel6Volume) + end + end end Player = Player Entity = Entity @@ -15,15 +47,15 @@ Entity = Entity if GetConvar('voice_useNativeAudio', 'false') == 'true' then -- native audio distance seems to be larger then regular gta units Cfg.voiceModes = { - {1.5, "Whisper"}, -- Whisper speech distance in gta distance units - {3.0, "Normal"}, -- Normal speech distance in gta distance units - {6.0, "Shouting"} -- Shout speech distance in gta distance units + { 1.5, "Whisper" }, -- Whisper speech distance in gta distance units + { 3.0, "Normal" }, -- Normal speech distance in gta distance units + { 6.0, "Shouting" } -- Shout speech distance in gta distance units } else Cfg.voiceModes = { - {3.0, "Whisper"}, -- Whisper speech distance in gta distance units - {7.0, "Normal"}, -- Normal speech distance in gta distance units - {15.0, "Shouting"} -- Shout speech distance in gta distance units + { 3.0, "Whisper" }, -- Whisper speech distance in gta distance units + { 7.0, "Normal" }, -- Normal speech distance in gta distance units + { 15.0, "Shouting" } -- Shout speech distance in gta distance units } end @@ -70,24 +102,26 @@ function tPrint(tbl, indent) end local function types(args) - local argType = type(args[1]) - for i = 2, #args do - local arg = args[i] - if argType == arg then - return true, argType - end - end - return false, argType + local argType = type(args[1]) + for i = 2, #args do + local arg = args[i] + if argType == arg then + return true, argType + end + end + return false, argType end +--- does a type check and errors if an invalid type is sent +---@param ... table a table with the variable being the first argument and the expected type being the second function type_check(...) - local vars = {...} - for i = 1, #vars do - local var = vars[i] - local matchesType, varType = types(var) - if not matchesType then - table.remove(var, 1) - error(("Invalid type sent to argument #%s, expected %s, got %s"):format(i, table.concat(var, "|"), varType)) - end - end -end \ No newline at end of file + local vars = { ... } + for i = 1, #vars do + local var = vars[i] + local matchesType, varType = types(var) + if not matchesType then + table.remove(var, 1) + error(("Invalid type sent to argument #%s, expected %s, got %s"):format(i, table.concat(var, "|"), varType)) + end + end +end diff --git a/server-data/resources/[phone]/pma-voice/voice-ui/pnpm-lock.yaml b/server-data/resources/[phone]/pma-voice/voice-ui/pnpm-lock.yaml index 62245e0e4..5e3c9a59f 100644 --- a/server-data/resources/[phone]/pma-voice/voice-ui/pnpm-lock.yaml +++ b/server-data/resources/[phone]/pma-voice/voice-ui/pnpm-lock.yaml @@ -33,15 +33,20 @@ packages: resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} engines: {node: '>=6.9.0'} - /@babel/helper-validator-identifier@7.22.5: - resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} + /@babel/helper-validator-identifier@7.15.7: + resolution: {integrity: sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} /@babel/highlight@7.16.0: resolution: {integrity: sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==} engines: {node: '>=6.9.0'} dependencies: - '@babel/helper-validator-identifier': 7.22.5 + '@babel/helper-validator-identifier': 7.15.7 chalk: 2.4.2 js-tokens: 4.0.0 dev: true @@ -51,14 +56,14 @@ packages: engines: {node: '>=6.0.0'} hasBin: true dependencies: - '@babel/types': 7.22.5 + '@babel/types': 7.23.0 - /@babel/types@7.22.5: - resolution: {integrity: sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==} + /@babel/types@7.23.0: + resolution: {integrity: sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==} engines: {node: '>=6.9.0'} dependencies: '@babel/helper-string-parser': 7.22.5 - '@babel/helper-validator-identifier': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 to-fast-properties: 2.0.0 /@hapi/address@2.1.4: @@ -1065,6 +1070,7 @@ packages: /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + requiresBuild: true dev: true optional: true @@ -1565,6 +1571,7 @@ packages: /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + requiresBuild: true dependencies: color-name: 1.1.4 dev: true @@ -1641,7 +1648,7 @@ packages: dev: true /concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true /concat-stream@1.6.2: @@ -1884,7 +1891,7 @@ packages: globby: 7.1.1 is-glob: 4.0.3 loader-utils: 1.4.0 - minimatch: 3.1.2 + minimatch: 3.0.4 normalize-path: 3.0.0 p-limit: 2.3.0 schema-utils: 1.0.0 @@ -1950,7 +1957,7 @@ packages: dependencies: nice-try: 1.0.5 path-key: 2.0.1 - semver: 5.7.2 + semver: 5.7.1 shebang-command: 1.2.0 which: 1.3.1 dev: true @@ -2829,6 +2836,7 @@ packages: /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} + requiresBuild: true dependencies: to-regex-range: 5.0.1 dev: true @@ -2888,8 +2896,8 @@ packages: readable-stream: 2.3.7 dev: true - /follow-redirects@1.15.2(debug@4.3.3): - resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + /follow-redirects@1.14.7(debug@4.3.3): + resolution: {integrity: sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -3044,6 +3052,7 @@ packages: /glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + requiresBuild: true dependencies: is-glob: 4.0.3 dev: true @@ -3059,7 +3068,7 @@ packages: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.0.4 once: 1.4.0 path-is-absolute: 1.0.1 dev: true @@ -3145,6 +3154,7 @@ packages: /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + requiresBuild: true dev: true /has-symbols@1.0.2: @@ -3372,7 +3382,7 @@ packages: engines: {node: '>=8.0.0'} dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.2(debug@4.3.3) + follow-redirects: 1.14.7(debug@4.3.3) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -3577,6 +3587,7 @@ packages: /is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} + requiresBuild: true dependencies: binary-extensions: 2.2.0 dev: true @@ -3729,6 +3740,7 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + requiresBuild: true dev: true /is-obj@2.0.0: @@ -3935,6 +3947,7 @@ packages: resolution: {integrity: sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==} engines: {node: '>=6'} hasBin: true + requiresBuild: true dependencies: minimist: 1.2.8 dev: true @@ -4027,6 +4040,7 @@ packages: /loader-utils@2.0.2: resolution: {integrity: sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==} engines: {node: '>=8.9.0'} + requiresBuild: true dependencies: big.js: 5.2.2 emojis-list: 3.0.0 @@ -4112,7 +4126,7 @@ packages: engines: {node: '>=6'} dependencies: pify: 4.0.1 - semver: 5.7.2 + semver: 5.7.1 dev: true /make-dir@3.1.0: @@ -4286,8 +4300,8 @@ packages: resolution: {integrity: sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=} dev: true - /minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + /minimatch@3.0.4: + resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==} dependencies: brace-expansion: 1.1.11 dev: true @@ -4477,7 +4491,7 @@ packages: dependencies: hosted-git-info: 2.8.9 resolve: 1.20.0 - semver: 5.7.2 + semver: 5.7.1 validate-npm-package-license: 3.0.4 dev: true @@ -5507,6 +5521,7 @@ packages: /readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + requiresBuild: true dependencies: picomatch: 2.3.0 dev: true @@ -5594,7 +5609,7 @@ packages: dev: true /requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolution: {integrity: sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=} dev: true /resolve-cwd@2.0.0: @@ -5717,8 +5732,8 @@ packages: node-forge: 0.10.0 dev: true - /semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} + /semver@5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} hasBin: true dev: true @@ -5911,7 +5926,7 @@ packages: faye-websocket: 0.11.4 inherits: 2.0.4 json3: 3.3.3 - url-parse: 1.5.10 + url-parse: 1.5.4 transitivePeerDependencies: - supports-color dev: true @@ -5963,7 +5978,7 @@ packages: dev: true /source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} engines: {node: '>=0.10.0'} dev: true @@ -6264,14 +6279,14 @@ packages: schema-utils: 1.0.0 serialize-javascript: 4.0.0 source-map: 0.6.1 - terser: 4.8.1 + terser: 4.8.0 webpack: 4.46.0 webpack-sources: 1.4.3 worker-farm: 1.7.0 dev: true - /terser@4.8.1: - resolution: {integrity: sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==} + /terser@4.8.0: + resolution: {integrity: sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==} engines: {node: '>=6.0.0'} hasBin: true dependencies: @@ -6354,6 +6369,7 @@ packages: /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + requiresBuild: true dependencies: is-number: 7.0.0 dev: true @@ -6537,8 +6553,8 @@ packages: webpack: 4.46.0 dev: true - /url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + /url-parse@1.5.4: + resolution: {integrity: sha512-ITeAByWWoqutFClc/lRZnFplgXgEZr3WJ6XngMM/N9DMIm4K8zXPCZ1Jdu0rERwO84w1WC5wkle2ubwTA4NTBg==} dependencies: querystringify: 2.2.0 requires-port: 1.0.0