Saltychat Remove and PMA install
This commit is contained in:
parent
0bff8ae174
commit
2fd3c1fe70
94 changed files with 8799 additions and 5199 deletions
21
resources/[voice]/pma-voice/LICENSE
Normal file
21
resources/[voice]/pma-voice/LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Dillon Skaggs
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
180
resources/[voice]/pma-voice/README.md
Normal file
180
resources/[voice]/pma-voice/README.md
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# pma-voice
|
||||
A voice system designed around the use of FiveM/RedM internal mumble server.
|
||||
|
||||
## Support
|
||||
|
||||
Please report any issues you have in the GitHub [Issues](https://github.com/AvarianKnight/pma-voice/issues)
|
||||
|
||||
### NOTE: It is expected for servers to be on the latest recommended version, which you can find [here for Windows](https://runtime.fivem.net/artifacts/fivem/build_server_windows/master/) and [here for Linux](https://runtime.fivem.net/artifacts/fivem/build_proot_linux/master/).
|
||||
|
||||
# Compatibility Notice:
|
||||
|
||||
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.
|
||||
|
||||
# Credits
|
||||
|
||||
- @Frazzle for mumble-voip (for which the concept came from)
|
||||
- @pichotm for pVoice (where the grid concept came from)
|
||||
|
||||
# FiveM/RedM Config
|
||||
|
||||
### NOTE: Only use one of the Audio options (don't enable 3d Audio & Native Audio at the same time), its also recommended to always use voice_useSendingRangeOnly.
|
||||
|
||||
You only need to add the convar **if** you're changing the value.
|
||||
|
||||
All of the configs here are set using `setr [voice_configOption] [boolean]`
|
||||
|
||||
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_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 |
|
||||
|
||||
# Config
|
||||
|
||||
### PLEASE NOTE: Any keybind changes only affect new players, if you want to change your key bind go to Key Bindings -> FiveM -> Look for keybinds under 'pma-voice'.
|
||||
|
||||
All of the config is done via ConVars in order to streamline the process.
|
||||
|
||||
The ints are used like a boolean to 0 would be false, 1 true.
|
||||
|
||||
All of the configs here are set using `setr [voice_configOption] [int]` OR `setr [voice_configOption] "[string]"`
|
||||
|
||||
#### Note: If a convar defaults to 1 (true) you don't have set it again unless you want to disable it.
|
||||
|
||||
### General Voice Settings
|
||||
|
||||
| ConVar | Default | Description | Parameter(s) |
|
||||
|-------------------------|---------|--------------------------------------------------------------------|--------------|
|
||||
| voice_enableUi | 1 | Enables the built in user interface | int |
|
||||
| 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_defaultVoiceMode | 2 | Default proximity voice value when player joins server. (Voice Modes; 1:Whisper, 2:Normal, 3:Shouting) | int |
|
||||
|
||||
### Phone & 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_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
|
||||
|
||||
| ConVar | Default | Description | Parameter(s) |
|
||||
|-------------------------|---------|--------------------------------------------------------------------|--------------|
|
||||
| voice_refreshRate | 200 | How often the UI/Proximity is refreshed | int |
|
||||
|
||||
### External Server & Misc.
|
||||
| ConVar | Default | Description | Parameter(s) |
|
||||
|-------------------------|---------|--------------------------------------------------------------------|--------------|
|
||||
| voice_allowSetIntent | 1 | Whether or not to allow players to set their audio intents (you can see more [here](https://docs.fivem.net/natives/?_0x6383526B)) | int |
|
||||
| voice_externalAddress | none | The external address to use to connect to the mumble server | string |
|
||||
| voice_externalPort | 0 | The external port to use | int |
|
||||
| voice_debugMode | 0 | 1 for basic logs, 4 for verbose logs | int |
|
||||
| voice_externalDisallowJoin | 0 | Disables players being allowed to join the server, should only be used if you're using a FXServer as a external mumble server. | int |
|
||||
| voice_hideEndpoints | 1 | Hides the mumble address in logs *NOTE: You should only care to hide this for a external server.* | int |
|
||||
|
||||
|
||||
|
||||
### Aces
|
||||
|
||||
pma-voice comes with a built in /muteply (tgtPly) (duration) command, in order to allow your staff to use it you will have to grand them the ace!
|
||||
|
||||
Example:
|
||||
`add_ace group.superadmin command.muteply allow;`
|
||||
|
||||
This would only allow the superadmin group to mute players.
|
||||
|
||||
### Exports
|
||||
|
||||
#### Client
|
||||
|
||||
##### Setters
|
||||
|
||||
| Export | Description | Parameter(s) |
|
||||
|---------------------|-----------------------------|--------------|
|
||||
| [setVoiceProperty](docs/client-setters/setVoiceProperty.md) | Set config options | string, any |
|
||||
| [setRadioChannel](docs/client-setters/setRadioChannel.md) | Set radio channel | int |
|
||||
| [setCallChannel](docs/client-setters/setCallChannel.md) | Set call channel | int |
|
||||
| [setRadioVolume](docs/client-setters/setRadioVolume.md) | Set radio volume for player | int |
|
||||
| [setCallVolume](docs/client-setters/setCallVolume.md) | Set call volume for player | int |
|
||||
| [addPlayerToRadio](docs/client-setters/setRadioChannel.md) | Set radio channel | int |
|
||||
| [addPlayerToCall](docs/client-setters/setCallChannel.md) | Set call channel | int |
|
||||
| [removePlayerFromRadio](docs/client-setters/removePlayerFromRadio.md) | Remove player from radio | |
|
||||
| [removePlayerFromCall](docs/client-setters/removePlayerFromCall.md) | Remove player from call | |
|
||||
|
||||
##### Toggles
|
||||
|
||||
| Export | Description | Parameter(s) |
|
||||
|---------------------|--------------------------------------------------------|--------------|
|
||||
| toggleMutePlayer | Toggles the selected player muted for the local client | int |
|
||||
|
||||
Supported from mumble-voip / toko-voip
|
||||
|
||||
| Export | Description | Parameter(s) |
|
||||
|-----------------------|--------------------------|--------------|
|
||||
| [SetMumbleProperty](docs/client-setters/setVoiceProperty.md) | Set config options | string, any |
|
||||
| [SetTokoProperty](docs/client-setters/setVoiceProperty.md) | Set config options | string, any |
|
||||
| [SetRadioChannel](docs/client-setters/setRadioChannel.md) | Set radio channel | int |
|
||||
| [SetCallChannel](docs/client-setters/setCallChannel.md) | Set call channel | int |
|
||||
|
||||
#### Getters
|
||||
|
||||
The majority of setters are done through player states, while a small
|
||||
|
||||
|
||||
| State Bag | Description | Return Type |
|
||||
|---------------|--------------------------------------------------------------|--------------|
|
||||
| [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 |
|
||||
|
||||
#### Events
|
||||
|
||||
These are events designed for third-party resource integration. These are emitted only to the current client.
|
||||
|
||||
| Event | Description | Event Params |
|
||||
|--------------------------|--------------------------------------------------------------|----------------|
|
||||
| [pma-voice:settingsCallback](docs/client-getters/events.md) | When emited it will return the current pma-voice settings. | cb(voiceSettings) |
|
||||
| [pma-voice:radioActive](docs/client-getters/events.md) | Triggered when the radio is activated / deactivated | boolean |
|
||||
| [pma-voice:setTalkingMode](docs/client-getters/events.md) | Triggered on proximity mode change with the voice mode id | int |
|
||||
|
||||
|
||||
#### Server
|
||||
|
||||
##### Setters
|
||||
|
||||
| Export | Description | Parameter(s) |
|
||||
|----------------------|--------------------------------------|--------------|
|
||||
| [setPlayerRadio](docs/server-setters/setPlayerRadio.md) | Sets the players radio channel | int, int |
|
||||
| [setPlayerCall](docs/server-setters/setPlayerCall.md) | Sets the players call channel | int, int |
|
||||
| [addChannelCheck](docs/server-setters/addChannelCheck.md) | Adds a channel check to the players radio channel | int, function |
|
||||
|
||||
|
||||
##### Getters
|
||||
|
||||
###### State Bags
|
||||
You can access the state with `Player(source).state['state bag here']`
|
||||
|
||||
| State Bag | Description | Return Type |
|
||||
|---------------|--------------------------------------------------------------|--------------|
|
||||
| [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 |
|
||||
| [voiceIntent](docs/state-getters/stateBagGetters.md) | Returns the players current voice intent, either 'speech' or 'music' | string |
|
||||
|
||||
###### Exports
|
||||
|
||||
| Export | Description | Parameter(s) |
|
||||
|------------------------------|---------------------------------------------------|------|
|
||||
| [getPlayersInRadioChannel](docs/server-getters/getPlayersInRadioChannel.md) | Gets the current players in a radio channel | int |
|
||||
10
resources/[voice]/pma-voice/TODO.md
Normal file
10
resources/[voice]/pma-voice/TODO.md
Normal file
|
|
@ -0,0 +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
|
||||
- [ ] Convert the UI to React.
|
||||
- [ ] 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
|
||||
74
resources/[voice]/pma-voice/client/commands.lua
Normal file
74
resources/[voice]/pma-voice/client/commands.lua
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
local wasProximityDisabledFromOverride = false
|
||||
disableProximityCycle = false
|
||||
RegisterCommand('setvoiceintent', function(source, args)
|
||||
if GetConvarInt('voice_allowSetIntent', 1) == 1 then
|
||||
local intent = args[1]
|
||||
if intent == 'speech' then
|
||||
MumbleSetAudioInputIntent(`speech`)
|
||||
elseif intent == 'music' then
|
||||
MumbleSetAudioInputIntent(`music`)
|
||||
end
|
||||
LocalPlayer.state:set('voiceIntent', intent, true)
|
||||
end
|
||||
end)
|
||||
|
||||
-- TODO: Better implementation of this?
|
||||
RegisterCommand('vol', function(_, args)
|
||||
if not args[1] then return end
|
||||
setVolume(tonumber(args[1]))
|
||||
end)
|
||||
|
||||
exports('setAllowProximityCycleState', function(state)
|
||||
type_check({state, "boolean"})
|
||||
disableProximityCycle = state
|
||||
end)
|
||||
|
||||
function setProximityState(proximityRange, isCustom)
|
||||
local voiceModeData = Cfg.voiceModes[mode]
|
||||
MumbleSetTalkerProximity(proximityRange + 0.0)
|
||||
LocalPlayer.state:set('proximity', {
|
||||
index = mode,
|
||||
distance = proximityRange,
|
||||
mode = isCustom and "Custom" or voiceModeData[2],
|
||||
}, true)
|
||||
sendUIMessage({
|
||||
-- JS expects this value to be - 1, "custom" voice is on the last index
|
||||
voiceMode = isCustom and #Cfg.voiceModes or mode - 1
|
||||
})
|
||||
end
|
||||
|
||||
exports("overrideProximityRange", function(range, disableCycle)
|
||||
type_check({range, "number"})
|
||||
setProximityState(range, true)
|
||||
if disableCycle then
|
||||
disableProximityCycle = true
|
||||
wasProximityDisabledFromOverride = true
|
||||
end
|
||||
end)
|
||||
|
||||
exports("clearProximityOverride", function()
|
||||
local voiceModeData = Cfg.voiceModes[mode]
|
||||
setProximityState(voiceModeData[1], false)
|
||||
if wasProximityDisabledFromOverride then
|
||||
disableProximityCycle = false
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterCommand('cycleproximity', function()
|
||||
-- Proximity is either disabled, or manually overwritten.
|
||||
if GetConvarInt('voice_enableProximityCycle', 1) ~= 1 or disableProximityCycle then return end
|
||||
local newMode = mode + 1
|
||||
|
||||
-- If we're within the range of our voice modes, allow the increase, otherwise reset to the first state
|
||||
if newMode <= #Cfg.voiceModes then
|
||||
mode = newMode
|
||||
else
|
||||
mode = 1
|
||||
end
|
||||
|
||||
setProximityState(Cfg.voiceModes[mode][1], false)
|
||||
TriggerEvent('pma-voice:setTalkingMode', mode)
|
||||
end, false)
|
||||
if gameVersion == 'fivem' then
|
||||
RegisterKeyMapping('cycleproximity', 'Cycle Proximity', 'keyboard', GetConvar('voice_defaultCycle', 'F11'))
|
||||
end
|
||||
41
resources/[voice]/pma-voice/client/events.lua
Normal file
41
resources/[voice]/pma-voice/client/events.lua
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
function handleInitialState()
|
||||
local voiceModeData = Cfg.voiceModes[mode]
|
||||
MumbleSetTalkerProximity(voiceModeData[1] + 0.0)
|
||||
MumbleClearVoiceTarget(voiceTarget)
|
||||
MumbleSetVoiceTarget(voiceTarget)
|
||||
MumbleSetVoiceChannel(playerServerId)
|
||||
|
||||
while MumbleGetVoiceChannelFromServerId(playerServerId) ~= playerServerId do
|
||||
Wait(250)
|
||||
end
|
||||
|
||||
MumbleAddVoiceTargetChannel(voiceTarget, playerServerId)
|
||||
|
||||
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.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],
|
||||
mode = voiceModeData[2],
|
||||
}, true)
|
||||
|
||||
handleInitialState()
|
||||
|
||||
logger.log('Finished connection logic')
|
||||
end)
|
||||
|
||||
AddEventHandler('mumbleDisconnected', function(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)
|
||||
42
resources/[voice]/pma-voice/client/init/init.lua
Normal file
42
resources/[voice]/pma-voice/client/init/init.lua
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
AddEventHandler('onClientResourceStart', function(resource)
|
||||
if resource ~= GetCurrentResourceName() then
|
||||
return
|
||||
end
|
||||
print('Starting script initialization')
|
||||
|
||||
-- Some people modify pma-voice and mess up the resource Kvp, which means that if someone
|
||||
-- joins another server that has pma-voice, it will error out, this will catch and fix the kvp.
|
||||
local success = pcall(function()
|
||||
local micClicksKvp = GetResourceKvpString('pma-voice_enableMicClicks')
|
||||
if not micClicksKvp then
|
||||
SetResourceKvp('pma-voice_enableMicClicks', tostring(true))
|
||||
else
|
||||
if micClicksKvp ~= 'true' and micClicksKvp ~= 'false' then
|
||||
error('Invalid Kvp, throwing error for automatic cleaning')
|
||||
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))
|
||||
micClicks = 'true'
|
||||
end
|
||||
sendUIMessage({
|
||||
uiEnabled = GetConvarInt("voice_enableUi", 1) == 1,
|
||||
voiceModes = json.encode(Cfg.voiceModes),
|
||||
voiceMode = mode - 1
|
||||
})
|
||||
|
||||
-- Reinitialize channels if they're set.
|
||||
if LocalPlayer.state.radioChannel ~= 0 then
|
||||
setRadioChannel(LocalPlayer.state.radioChannel)
|
||||
end
|
||||
|
||||
if LocalPlayer.state.callChannel ~= 0 then
|
||||
setCallChannel(LocalPlayer.state.callChannel)
|
||||
end
|
||||
print('Script initialization finished.')
|
||||
end)
|
||||
224
resources/[voice]/pma-voice/client/init/main.lua
Normal file
224
resources/[voice]/pma-voice/client/init/main.lua
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
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,
|
||||
}
|
||||
|
||||
radioEnabled, radioPressed, mode = true, false, GetConvarInt('voice_defaultVoiceMode', 2)
|
||||
radioData = {}
|
||||
callData = {}
|
||||
|
||||
--- 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
|
||||
|
||||
if volumeType then
|
||||
local volumeTbl = volumes[volumeType]
|
||||
if volumeTbl then
|
||||
LocalPlayer.state:set(volumeType, volume, true)
|
||||
volumes[volumeType] = volume
|
||||
else
|
||||
error(('setVolume got a invalid volume type %s'):format(volumeType))
|
||||
end
|
||||
else
|
||||
-- _ is here to not mess with global 'type' function
|
||||
for _type, vol in pairs(volumes) do
|
||||
volumes[_type] = volume
|
||||
LocalPlayer.state:set(_type, volume, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
exports('setRadioVolume', function(vol)
|
||||
setVolume(vol, 'radio')
|
||||
end)
|
||||
exports('getRadioVolume', function()
|
||||
return volumes['radio']
|
||||
end)
|
||||
exports("setCallVolume", function(vol)
|
||||
setVolume(vol, 'phone')
|
||||
end)
|
||||
exports('getCallVolume', function()
|
||||
return volumes['phone']
|
||||
end)
|
||||
|
||||
|
||||
-- default submix incase people want to fiddle with it.
|
||||
-- freq_low = 389.0
|
||||
-- freq_hi = 3248.0
|
||||
-- fudge = 0.0
|
||||
-- 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
|
||||
|
||||
local submixFunctions = {
|
||||
['radio'] = function(plySource)
|
||||
MumbleSetSubmixForServerId(plySource, radioEffectId)
|
||||
end,
|
||||
['phone'] = function(plySource)
|
||||
MumbleSetSubmixForServerId(plySource, phoneEffectId)
|
||||
end
|
||||
}
|
||||
|
||||
-- used to prevent a race condition if they talk again afterwards, which would lead to their voice going to default.
|
||||
local disableSubmixReset = {}
|
||||
--- function toggleVoice
|
||||
--- Toggles the players voice
|
||||
---@param plySource number the players server id to override the volume for
|
||||
---@param enabled boolean if the players voice is getting activated or deactivated
|
||||
---@param moduleType string the volume & submix to use for the voice.
|
||||
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
|
||||
MumbleSetVolumeOverrideByServerId(plySource, enabled and volumes[moduleType])
|
||||
if GetConvarInt('voice_enableSubmix', 1) == 1 and gameVersion == 'fivem' then
|
||||
if moduleType then
|
||||
disableSubmixReset[plySource] = true
|
||||
submixFunctions[moduleType](plySource)
|
||||
else
|
||||
MumbleSetSubmixForServerId(plySource, -1)
|
||||
end
|
||||
end
|
||||
else
|
||||
if GetConvarInt('voice_enableSubmix', 1) == 1 and gameVersion == 'fivem' then
|
||||
-- garbage collect it
|
||||
disableSubmixReset[plySource] = nil
|
||||
SetTimeout(250, function()
|
||||
if not disableSubmixReset[plySource] then
|
||||
MumbleSetSubmixForServerId(plySource, -1)
|
||||
end
|
||||
end)
|
||||
end
|
||||
MumbleSetVolumeOverrideByServerId(plySource, -1.0)
|
||||
end
|
||||
end
|
||||
|
||||
--- function playerTargets
|
||||
---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 = {...}
|
||||
local addedPlayers = {
|
||||
[playerServerId] = true
|
||||
}
|
||||
|
||||
for i = 1, #targets do
|
||||
for id, _ in pairs(targets[i]) do
|
||||
-- we don't want to log ourself, or listen to ourself
|
||||
if addedPlayers[id] and id ~= playerServerId then
|
||||
logger.verbose('[main] %s is already target don\'t re-add', id)
|
||||
goto skip_loop
|
||||
end
|
||||
if not addedPlayers[id] then
|
||||
logger.verbose('[main] Adding %s as a voice target', id)
|
||||
addedPlayers[id] = true
|
||||
MumbleAddVoiceTargetPlayerByServerId(voiceTarget, id)
|
||||
end
|
||||
::skip_loop::
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- function playMicClicks
|
||||
---plays the mic click if the player has them enabled.
|
||||
---@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
|
||||
sendUIMessage({
|
||||
sound = (clickType and "audio_on" or "audio_off"),
|
||||
volume = (clickType and volumes["radio"] or 0.05)
|
||||
})
|
||||
end
|
||||
|
||||
--- toggles the targeted player muted
|
||||
---@param source number the player to mute
|
||||
function toggleMutePlayer(source)
|
||||
if mutedPlayers[source] then
|
||||
mutedPlayers[source] = nil
|
||||
MumbleSetVolumeOverrideByServerId(source, -1.0)
|
||||
else
|
||||
mutedPlayers[source] = true
|
||||
MumbleSetVolumeOverrideByServerId(source, 0.0)
|
||||
end
|
||||
end
|
||||
exports('toggleMutePlayer', toggleMutePlayer)
|
||||
|
||||
--- function setVoiceProperty
|
||||
--- sets the specified voice property
|
||||
---@param type string what voice property you want to change (only takes 'radioEnabled' and 'micClicks')
|
||||
---@param value any the value to set the type to.
|
||||
function setVoiceProperty(type, value)
|
||||
if type == "radioEnabled" then
|
||||
radioEnabled = value
|
||||
sendUIMessage({
|
||||
radioEnabled = value
|
||||
})
|
||||
elseif type == "micClicks" then
|
||||
local val = tostring(value)
|
||||
micClicks = val
|
||||
SetResourceKvp('pma-voice_enableMicClicks', val)
|
||||
end
|
||||
end
|
||||
exports('setVoiceProperty', setVoiceProperty)
|
||||
-- compatibility
|
||||
exports('SetMumbleProperty', setVoiceProperty)
|
||||
exports('SetTokoProperty', setVoiceProperty)
|
||||
|
||||
|
||||
-- cache their external servers so if it changes in runtime we can reconnect the client.
|
||||
local externalAddress = ''
|
||||
local externalPort = 0
|
||||
CreateThread(function()
|
||||
while true do
|
||||
Wait(500)
|
||||
-- only change if what we have doesn't match the cache
|
||||
if GetConvar('voice_externalAddress', '') ~= externalAddress or GetConvarInt('voice_externalPort', 0) ~= externalPort then
|
||||
externalAddress = GetConvar('voice_externalAddress', '')
|
||||
externalPort = GetConvarInt('voice_externalPort', 0)
|
||||
MumbleSetServerAddress(GetConvar('voice_externalAddress', ''), GetConvarInt('voice_externalPort', 0))
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
if gameVersion == 'redm' then
|
||||
CreateThread(function()
|
||||
while true do
|
||||
if IsControlJustPressed(0, 0xA5BDCD3C --[[ Right Bracket ]]) then
|
||||
ExecuteCommand('cycleproximity')
|
||||
end
|
||||
if IsControlJustPressed(0, 0x430593AA --[[ Left Bracket ]]) then
|
||||
ExecuteCommand('+radiotalk')
|
||||
elseif IsControlJustReleased(0, 0x430593AA --[[ Left Bracket ]]) then
|
||||
ExecuteCommand('-radiotalk')
|
||||
end
|
||||
|
||||
Wait(0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
156
resources/[voice]/pma-voice/client/init/proximity.lua
Normal file
156
resources/[voice]/pma-voice/client/init/proximity.lua
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
-- used when muted
|
||||
local disableUpdates = false
|
||||
local isListenerEnabled = false
|
||||
local plyCoords = GetEntityCoords(PlayerPedId())
|
||||
|
||||
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
|
||||
end
|
||||
local addProximityCheck = orig_addProximityCheck
|
||||
|
||||
exports("overrideProximityCheck", function(fn)
|
||||
addProximityCheck = fn
|
||||
end)
|
||||
|
||||
exports("resetProximityCheck", function()
|
||||
addProximityCheck = orig_addProximityCheck
|
||||
end)
|
||||
|
||||
function addNearbyPlayers()
|
||||
if disableUpdates then return end
|
||||
-- update here so we don't have to update every call of addProximityCheck
|
||||
plyCoords = GetEntityCoords(PlayerPedId())
|
||||
|
||||
MumbleClearVoiceTargetChannels(voiceTarget)
|
||||
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)
|
||||
end
|
||||
|
||||
::skip_loop::
|
||||
end
|
||||
end
|
||||
|
||||
function setSpectatorMode(enabled)
|
||||
logger.info('Setting spectate mode to %s', enabled)
|
||||
isListenerEnabled = enabled
|
||||
local players = GetActivePlayers()
|
||||
if isListenerEnabled then
|
||||
for i = 1, #players do
|
||||
local ply = players[i]
|
||||
local serverId = GetPlayerServerId(ply)
|
||||
if serverId == playerServerId then goto skip_loop end
|
||||
logger.verbose("Adding %s to listen table", serverId)
|
||||
MumbleAddVoiceChannelListen(serverId)
|
||||
::skip_loop::
|
||||
end
|
||||
else
|
||||
for i = 1, #players do
|
||||
local ply = players[i]
|
||||
local serverId = GetPlayerServerId(ply)
|
||||
if serverId == playerServerId then goto skip_loop end
|
||||
logger.verbose("Removing %s from listen table", serverId)
|
||||
MumbleRemoveVoiceChannelListen(serverId)
|
||||
::skip_loop::
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
RegisterNetEvent('onPlayerJoining', function(serverId)
|
||||
if isListenerEnabled then
|
||||
MumbleAddVoiceChannelListen(serverId)
|
||||
logger.verbose("Adding %s to listen table", serverId)
|
||||
end
|
||||
end)
|
||||
|
||||
RegisterNetEvent('onPlayerDropped', function(serverId)
|
||||
if isListenerEnabled then
|
||||
MumbleRemoveVoiceChannelListen(serverId)
|
||||
logger.verbose("Removing %s from listen table", serverId)
|
||||
end
|
||||
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()
|
||||
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)" }
|
||||
})
|
||||
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
|
||||
if GetConvarInt('voice_enableUi', 1) == 1 then
|
||||
local curTalkingStatus = MumbleIsPlayerTalking(PlayerId()) == 1
|
||||
if lastRadioStatus ~= radioPressed or lastTalkingStatus ~= curTalkingStatus then
|
||||
lastRadioStatus = radioPressed
|
||||
lastTalkingStatus = curTalkingStatus
|
||||
sendUIMessage({
|
||||
usingRadio = lastRadioStatus,
|
||||
talking = lastTalkingStatus
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if voiceState == "proximity" then
|
||||
addNearbyPlayers()
|
||||
local isSpectating = NetworkIsInSpectatorMode()
|
||||
if isSpectating and not isListenerEnabled then
|
||||
setSpectatorMode(true)
|
||||
elseif not isSpectating and isListenerEnabled then
|
||||
setSpectatorMode(false)
|
||||
end
|
||||
end
|
||||
|
||||
Wait(GetConvarInt('voice_refreshRate', 200))
|
||||
end
|
||||
end)
|
||||
|
||||
exports("setVoiceState", function(_voiceState, channel)
|
||||
if _voiceState ~= "proximity" and _voiceState ~= "channel" then
|
||||
logger.error("Didn't get a proper voice state, expected proximity or channel, got %s", _voiceState)
|
||||
end
|
||||
voiceState = _voiceState
|
||||
if voiceState == "channel" then
|
||||
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)
|
||||
while MumbleGetVoiceChannelFromServerId(playerServerId) ~= channel do
|
||||
Wait(250)
|
||||
end
|
||||
MumbleAddVoiceTargetChannel(voiceTarget, channel)
|
||||
elseif voiceState == "proximity" then
|
||||
handleInitialState()
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
AddEventHandler("onClientResourceStop", function(resource)
|
||||
if type(addProximityCheck) == "table" then
|
||||
local proximityCheckRef = addProximityCheck.__cfx_functionReference
|
||||
if proximityCheckRef then
|
||||
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)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
91
resources/[voice]/pma-voice/client/module/phone.lua
Normal file
91
resources/[voice]/pma-voice/client/module/phone.lua
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
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
|
||||
end)
|
||||
|
||||
RegisterNetEvent('pma-voice:addPlayerToCall', function(plySource)
|
||||
callData[plySource] = false
|
||||
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')
|
||||
end
|
||||
end
|
||||
callData = {}
|
||||
MumbleClearVoiceTargetPlayers(voiceTarget)
|
||||
playerTargets(radioPressed and radioData or {}, callData)
|
||||
else
|
||||
callData[plySource] = nil
|
||||
toggleVoice(plySource, false, 'phone')
|
||||
if MumbleIsPlayerTalking(PlayerId()) then
|
||||
MumbleClearVoiceTargetPlayers(voiceTarget)
|
||||
playerTargets(radioPressed and radioData or {}, callData)
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
function setCallChannel(channel)
|
||||
if GetConvarInt('voice_enablePhones', 1) ~= 1 then return end
|
||||
TriggerServerEvent('pma-voice:setPlayerCall', channel)
|
||||
callChannel = channel
|
||||
sendUIMessage({
|
||||
callInfo = channel
|
||||
})
|
||||
createPhoneThread()
|
||||
end
|
||||
|
||||
exports('setCallChannel', setCallChannel)
|
||||
exports('SetCallChannel', setCallChannel)
|
||||
|
||||
exports('addPlayerToCall', function(_call)
|
||||
local call = tonumber(_call)
|
||||
if call then
|
||||
setCallChannel(call)
|
||||
end
|
||||
end)
|
||||
exports('removePlayerFromCall', function()
|
||||
setCallChannel(0)
|
||||
end)
|
||||
|
||||
RegisterNetEvent('pma-voice:clSetPlayerCall', function(_callChannel)
|
||||
if GetConvarInt('voice_enablePhones', 1) ~= 1 then return end
|
||||
callChannel = _callChannel
|
||||
createPhoneThread()
|
||||
end)
|
||||
211
resources/[voice]/pma-voice/client/module/radio.lua
Normal file
211
resources/[voice]/pma-voice/client/module/radio.lua
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
local radioChannel = 0
|
||||
local radioNames = {}
|
||||
local disableRadioAnim = false
|
||||
|
||||
--- event syncRadioData
|
||||
--- syncs the current players on the radio to the client
|
||||
---@param radioTable table the table of the current players on the radio
|
||||
---@param localPlyRadioName string the local players name
|
||||
function syncRadioData(radioTable, localPlyRadioName)
|
||||
radioData = radioTable
|
||||
logger.info('[radio] Syncing radio table.')
|
||||
if GetConvarInt('voice_debugMode', 0) >= 4 then
|
||||
print('-------- RADIO TABLE --------')
|
||||
tPrint(radioData)
|
||||
print('-----------------------------')
|
||||
end
|
||||
for tgt, enabled in pairs(radioTable) do
|
||||
if tgt ~= playerServerId then
|
||||
toggleVoice(tgt, enabled, 'radio')
|
||||
end
|
||||
end
|
||||
sendUIMessage({
|
||||
radioChannel = radioChannel,
|
||||
radioEnabled = radioEnabled
|
||||
})
|
||||
if GetConvarInt("voice_syncPlayerNames", 0) == 1 then
|
||||
radioNames[playerServerId] = localPlyRadioName
|
||||
end
|
||||
end
|
||||
RegisterNetEvent('pma-voice:syncRadioData', syncRadioData)
|
||||
|
||||
--- event setTalkingOnRadio
|
||||
--- sets the players talking status, triggered when a player starts/stops talking.
|
||||
---@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
|
||||
playMicClicks(enabled)
|
||||
end
|
||||
RegisterNetEvent('pma-voice:setTalkingOnRadio', setTalkingOnRadio)
|
||||
|
||||
--- event addPlayerToRadio
|
||||
--- adds a player onto the radio.
|
||||
---@param plySource number the players server id to add to the radio.
|
||||
function addPlayerToRadio(plySource, plyRadioName)
|
||||
radioData[plySource] = false
|
||||
if GetConvarInt("voice_syncPlayerNames", 0) == 1 then
|
||||
radioNames[plySource] = plyRadioName
|
||||
end
|
||||
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)
|
||||
end
|
||||
end
|
||||
RegisterNetEvent('pma-voice:addPlayerToRadio', addPlayerToRadio)
|
||||
|
||||
--- event removePlayerFromRadio
|
||||
--- removes the player (or self) from the radio
|
||||
---@param plySource number the players server id to remove from the radio.
|
||||
function removePlayerFromRadio(plySource)
|
||||
if plySource == playerServerId then
|
||||
logger.info('[radio] Left radio %s, cleaning up.', radioChannel)
|
||||
for tgt, _ in pairs(radioData) do
|
||||
if tgt ~= playerServerId then
|
||||
toggleVoice(tgt, false, 'radio')
|
||||
end
|
||||
end
|
||||
sendUIMessage({
|
||||
radioChannel = 0,
|
||||
radioEnabled = radioEnabled
|
||||
})
|
||||
radioNames = {}
|
||||
radioData = {}
|
||||
playerTargets(MumbleIsPlayerTalking(PlayerId()) and callData or {})
|
||||
else
|
||||
toggleVoice(plySource, false)
|
||||
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 {})
|
||||
else
|
||||
logger.info('[radio] %s has left radio %s', plySource, radioChannel)
|
||||
end
|
||||
radioData[plySource] = nil
|
||||
if GetConvarInt("voice_syncPlayerNames", 0) == 1 then
|
||||
radioNames[plySource] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
RegisterNetEvent('pma-voice:removePlayerFromRadio', removePlayerFromRadio)
|
||||
|
||||
--- 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"})
|
||||
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)
|
||||
|
||||
--- exports removePlayerFromRadio
|
||||
--- sets the local players current radio channel and updates the server
|
||||
exports('removePlayerFromRadio', function()
|
||||
setRadioChannel(0)
|
||||
end)
|
||||
|
||||
--- exports addPlayerToRadio
|
||||
--- sets the local players current radio channel and updates the server
|
||||
---@param _radio number the channel to set the player to, or 0 to remove them.
|
||||
exports('addPlayerToRadio', function(_radio)
|
||||
local radio = tonumber(_radio)
|
||||
if radio then
|
||||
setRadioChannel(radio)
|
||||
end
|
||||
end)
|
||||
|
||||
--- exports toggleRadioAnim
|
||||
--- toggles whether the client should play radio anim or not, if the animation should be played or notvaliddance
|
||||
exports('toggleRadioAnim', function()
|
||||
disableRadioAnim = not disableRadioAnim
|
||||
TriggerEvent('pma-voice:toggleRadioAnim', disableRadioAnim)
|
||||
end)
|
||||
|
||||
-- exports disableRadioAnim
|
||||
--- returns whether the client is undercover or not
|
||||
exports('getRadioAnimState', function()
|
||||
return toggleRadioAnim
|
||||
end)
|
||||
|
||||
--- check if the player is dead
|
||||
--- seperating this so if people use different methods they can customize
|
||||
--- it to their need as this will likely never be changed
|
||||
--- but you can integrate the below state bag to your death resources.
|
||||
--- LocalPlayer.state:set('isDead', true or false, false)
|
||||
function isDead()
|
||||
if LocalPlayer.state.isDead then
|
||||
return true
|
||||
elseif IsPlayerDead(PlayerId()) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
RegisterCommand('+radiotalk', function()
|
||||
if GetConvarInt('voice_enableRadios', 1) ~= 1 then return end
|
||||
if isDead() 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 {})
|
||||
TriggerServerEvent('pma-voice:setTalkingOnRadio', true)
|
||||
radioPressed = true
|
||||
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
|
||||
end
|
||||
Citizen.CreateThread(function()
|
||||
TriggerEvent("pma-voice:radioActive", true)
|
||||
while radioPressed do
|
||||
Wait(0)
|
||||
SetControlNormal(0, 249, 1.0)
|
||||
SetControlNormal(1, 249, 1.0)
|
||||
SetControlNormal(2, 249, 1.0)
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end, false)
|
||||
|
||||
RegisterCommand('-radiotalk', function()
|
||||
if radioChannel > 0 or radioEnabled and radioPressed then
|
||||
radioPressed = false
|
||||
MumbleClearVoiceTargetPlayers(voiceTarget)
|
||||
playerTargets(MumbleIsPlayerTalking(PlayerId()) and callData or {})
|
||||
TriggerEvent("pma-voice:radioActive", false)
|
||||
playMicClicks(false)
|
||||
if GetConvarInt('voice_enableRadioAnim', 0) == 1 then
|
||||
StopAnimTask(PlayerPedId(), "random@arrests", "generic_radio_enter", -4.0)
|
||||
end
|
||||
TriggerServerEvent('pma-voice:setTalkingOnRadio', false)
|
||||
end
|
||||
end, false)
|
||||
if gameVersion == 'fivem' then
|
||||
RegisterKeyMapping('+radiotalk', 'Talk over Radio', 'keyboard', GetConvar('voice_defaultRadio', 'LMENU'))
|
||||
end
|
||||
|
||||
--- event syncRadio
|
||||
--- syncs the players radio, only happens if the radio was set server side.
|
||||
---@param _radioChannel number the radio channel to set the player to.
|
||||
function syncRadio(_radioChannel)
|
||||
if GetConvarInt('voice_enableRadios', 1) ~= 1 then return end
|
||||
logger.info('[radio] radio set serverside update to radio %s', radioChannel)
|
||||
radioChannel = _radioChannel
|
||||
end
|
||||
RegisterNetEvent('pma-voice:clSetPlayerRadio', syncRadio)
|
||||
11
resources/[voice]/pma-voice/client/utils/Nui.lua
Normal file
11
resources/[voice]/pma-voice/client/utils/Nui.lua
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
local uiReady = promise.new()
|
||||
function sendUIMessage(message)
|
||||
Citizen.Await(uiReady)
|
||||
SendNUIMessage(message)
|
||||
end
|
||||
|
||||
RegisterNUICallback("uiReady", function(data, cb)
|
||||
uiReady:resolve(true)
|
||||
|
||||
cb('ok')
|
||||
end)
|
||||
1
resources/[voice]/pma-voice/docs/_config.yml
Normal file
1
resources/[voice]/pma-voice/docs/_config.yml
Normal file
|
|
@ -0,0 +1 @@
|
|||
theme: jekyll-theme-midnight
|
||||
27
resources/[voice]/pma-voice/docs/client-getters/events.md
Normal file
27
resources/[voice]/pma-voice/docs/client-getters/events.md
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
## setTalkingMode | settingsCallback | radioACtive
|
||||
|
||||
## Description
|
||||
|
||||
These event is designed to allow third part applications (like a hud) use the current voice mode of the player, radio state, etc.
|
||||
|
||||
```lua
|
||||
-- default voice mode is 2
|
||||
local voiceMode = 2
|
||||
local voiceModes = {}
|
||||
local usingRadio = false
|
||||
-- sets the current radio state boolean
|
||||
AddEventHandler("pma-voice:radioActive", function(radioTalking) usingRadio = radioTalking end)
|
||||
-- changes the current voice range index
|
||||
AddEventHandler('pma-voice:setTalkingMode', function(newTalkingRange) voiceMode = newTalkingRange end)
|
||||
-- returns registered voice modes from shared.lua's `Cfg.voiceModes`
|
||||
TriggerEvent("pma-voice:settingsCallback", function(voiceSettings)
|
||||
local voiceTable = voiceSettings.voiceModes
|
||||
|
||||
-- loop through all voice modes and add them to the table
|
||||
-- the percentage is used for the voice mode slider if this was an actual UI
|
||||
for i = 1, #voiceTable do
|
||||
local distance = math.ceil(((i/#voiceTable) * 100))
|
||||
voiceModes[i] = ("%s"):format(distance)
|
||||
end
|
||||
end)
|
||||
```
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
## removePlayerFromCall
|
||||
|
||||
## Description
|
||||
|
||||
Removes the player from the call
|
||||
|
||||
## NOTE: This is just syntactic sugar for `setCallChannel(0)`
|
||||
|
||||
```lua
|
||||
-- Removes the player from the call channel
|
||||
exports['pma-voice']:removePlayerFromCall()
|
||||
```
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
## removePlayerFromRadio
|
||||
|
||||
## Description
|
||||
|
||||
Removes the player from the radio
|
||||
|
||||
## NOTE: This is just syntactic sugar for `setRadioChannel(0)`
|
||||
|
||||
```lua
|
||||
-- Removes the player from the radio channel
|
||||
exports['pma-voice']:removePlayerFromRadio()
|
||||
```
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
## setCallChannel | addPlayerToCall | SetCallChannel
|
||||
|
||||
## Description
|
||||
|
||||
Sets the local players call channel.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **callChannel**: the call channel to join
|
||||
|
||||
|
||||
```lua
|
||||
-- Joins call channel 1
|
||||
exports['pma-voice']:setCallChannel(1)
|
||||
|
||||
-- This will remove them from the call channel
|
||||
exports['pma-voice']:setCallChannel(0)
|
||||
```
|
||||
|
||||
addPlayerToCall is provided as a 'easier to read' version of setCallChannel.
|
||||
|
||||
```lua
|
||||
-- Joins call channel 1
|
||||
exports['pma-voice']:addPlayerToCall(1)
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
## setCallVolume
|
||||
|
||||
## Description
|
||||
|
||||
Sets the local players call channel volume
|
||||
|
||||
## Parameters
|
||||
|
||||
* **callVolume**: the call volume to set to between 0 - 100 percent
|
||||
|
||||
```lua
|
||||
-- set the call volume to 50 percent
|
||||
exports['pma-voice']:setCallVolume(50)
|
||||
```
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
## setRadioChannel | addPlayerToRadio | SetCallChannel
|
||||
|
||||
## Description
|
||||
|
||||
Sets the local players radio channel.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **radioChannel**: the radio channel to join
|
||||
|
||||
## NOTE: If the player fails the server side radio channel check they will be reset to no channel.
|
||||
|
||||
```lua
|
||||
-- Joins radio channel 1
|
||||
exports['pma-voice']:setRadioChannel(1)
|
||||
|
||||
-- This will remove the player from all radio channels
|
||||
expots ['pma-voice']:setRadioChannel(0)
|
||||
```
|
||||
|
||||
addPlayerToRadio is provided as a 'easier to read' alternative to setRadioChannel.
|
||||
|
||||
```lua
|
||||
-- Joins radio channel 1
|
||||
exports['pma-voice']:addPlayerToRadio(1)
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
## setRadioVolume
|
||||
|
||||
## Description
|
||||
|
||||
Sets the local players radio channel volume
|
||||
|
||||
## Parameters
|
||||
|
||||
* **radioVolume**: the radio volume to set to between 0 - 100 percent
|
||||
|
||||
```lua
|
||||
-- sets the radio volume to 50 percent
|
||||
exports['pma-voice']:setRadioVolume(50)
|
||||
```
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
## setVoiceProperty | SetMumbleProperty | SetTokoProperty
|
||||
|
||||
## Description
|
||||
|
||||
Sets the voice property, currently the only use is to enable/disable radios and radio clicks.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **property**: The property to set
|
||||
* **value**: The value to set the property to
|
||||
|
||||
```lua
|
||||
-- Enable the radio
|
||||
exports['pma-voice']:setVoiceProperty('radioEnabled', true)
|
||||
-- Disable radio clicks
|
||||
exports['pma-voice']:setVoiceProperty('micClicks', false)
|
||||
```
|
||||
3
resources/[voice]/pma-voice/docs/routingBuckets.md
Normal file
3
resources/[voice]/pma-voice/docs/routingBuckets.md
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
## Routing Buckets
|
||||
|
||||
pma-voice natively supports routing buckets.
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
## getPlayersInRadioChannel
|
||||
|
||||
## Description
|
||||
|
||||
Gets a list of all of the players in the specified radio channel.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **radioChannel**: The channel to get all the members of
|
||||
|
||||
## Returns
|
||||
|
||||
Returns a table of all of the players in the specified radio channel
|
||||
|
||||
```lua
|
||||
-- this will return all of the current players in radio channel 1
|
||||
local players = exports['pma-voice']:getPlayersInRadioChannel(1)
|
||||
for source, isTalking in pairs(players) do
|
||||
print(('%s is in radio channel 1, isTalking: %s'):format(GetPlayerName(source), isTalking))
|
||||
end
|
||||
```
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
## addChannelCheck
|
||||
|
||||
## Description
|
||||
|
||||
Adds a channel check to radio channels.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **channel**: The channel to add the check to.
|
||||
* **function**: the function to call when the check is triggered, which should return a boolean of if the player is allowed to join the channel..
|
||||
|
||||
|
||||
```lua
|
||||
-- Example for addChannelCheck
|
||||
-- this always has to return true/false
|
||||
exports['pma-voice']:addChannelCheck(1, function(source)
|
||||
if IsPlayerAceAllowed(source, 'radio.police') then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
## setPlayerCall
|
||||
|
||||
## Description
|
||||
|
||||
Sets the players call channel.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **source**: The player to set the radio channel of
|
||||
* **callChannel**: the radio channel to set the player to
|
||||
|
||||
```lua
|
||||
exports['pma-voice']:setPlayerCall(source, 1)
|
||||
```
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
## setPlayerRadio
|
||||
|
||||
## Description
|
||||
|
||||
Sets the players radio channel.
|
||||
|
||||
## Parameters
|
||||
|
||||
* **source**: The player to set the radio channel of
|
||||
* **radioChannel**: the radio channel to set the player to
|
||||
|
||||
```lua
|
||||
exports['pma-voice']:setPlayerRadio(source, 1)
|
||||
```
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
## State Bag Getters/Setters
|
||||
|
||||
## Description
|
||||
|
||||
State bag getters are a little bit simpler, they just return the current value that is set in the state bag.
|
||||
|
||||
#### Note: If you're on the client and only using it on the current player, you can replace Player(source) with LocalPlayer
|
||||
|
||||
## Example for Proximity
|
||||
|
||||
```lua
|
||||
local plyState = Player(source).state
|
||||
local proximity = plyState.proximity
|
||||
print(proximity.index) -- prints the index of the proximity as seen in Cfg.voiceModes
|
||||
print(proximity.distance) -- prints the distance of the proximity
|
||||
print(proximity.mode) -- prints the mode name of the proximity
|
||||
```
|
||||
69
resources/[voice]/pma-voice/fxmanifest.lua
Normal file
69
resources/[voice]/pma-voice/fxmanifest.lua
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
game 'common'
|
||||
|
||||
fx_version 'cerulean'
|
||||
author 'AvarianKnight'
|
||||
description 'VOIP built using FiveM\'s built in mumble.'
|
||||
|
||||
dependencies {
|
||||
'/onesync',
|
||||
}
|
||||
|
||||
lua54 'yes'
|
||||
|
||||
shared_script 'shared.lua'
|
||||
|
||||
client_scripts {
|
||||
'client/utils/*',
|
||||
'client/init/proximity.lua',
|
||||
'client/init/init.lua',
|
||||
'client/init/main.lua',
|
||||
'client/module/*.lua',
|
||||
'client/*.lua',
|
||||
}
|
||||
|
||||
server_scripts {
|
||||
'server/**/*.lua',
|
||||
'server/**/*.js'
|
||||
}
|
||||
|
||||
files {
|
||||
'ui/*.ogg',
|
||||
'ui/css/*.css',
|
||||
'ui/js/*.js',
|
||||
'ui/index.html',
|
||||
}
|
||||
|
||||
ui_page 'ui/index.html'
|
||||
|
||||
provides {
|
||||
'mumble-voip',
|
||||
'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" },
|
||||
}
|
||||
}
|
||||
139
resources/[voice]/pma-voice/server/main.lua
Normal file
139
resources/[voice]/pma-voice/server/main.lua
Normal 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)
|
||||
94
resources/[voice]/pma-voice/server/module/phone.lua
Normal file
94
resources/[voice]/pma-voice/server/module/phone.lua
Normal 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)
|
||||
165
resources/[voice]/pma-voice/server/module/radio.lua
Normal file
165
resources/[voice]/pma-voice/server/module/radio.lua
Normal 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)
|
||||
26
resources/[voice]/pma-voice/server/mute.js
Normal file
26
resources/[voice]/pma-voice/server/mute.js
Normal 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)
|
||||
93
resources/[voice]/pma-voice/shared.lua
Normal file
93
resources/[voice]/pma-voice/shared.lua
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
Cfg = {}
|
||||
|
||||
voiceTarget = 1
|
||||
|
||||
gameVersion = GetGameName()
|
||||
|
||||
-- these are just here to satisfy linting
|
||||
if not IsDuplicityVersion() then
|
||||
LocalPlayer = LocalPlayer
|
||||
playerServerId = GetPlayerServerId(PlayerId())
|
||||
end
|
||||
Player = Player
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
end
|
||||
|
||||
logger = {
|
||||
log = function(message, ...)
|
||||
print((message):format(...))
|
||||
end,
|
||||
info = function(message, ...)
|
||||
if GetConvarInt('voice_debugMode', 0) >= 1 then
|
||||
print(('[info] ' .. message):format(...))
|
||||
end
|
||||
end,
|
||||
warn = function(message, ...)
|
||||
print(('[^1WARNING^7] ' .. message):format(...))
|
||||
end,
|
||||
error = function(message, ...)
|
||||
error((message):format(...))
|
||||
end,
|
||||
verbose = function(message, ...)
|
||||
if GetConvarInt('voice_debugMode', 0) >= 4 then
|
||||
print(('[verbose] ' .. message):format(...))
|
||||
end
|
||||
end,
|
||||
}
|
||||
|
||||
|
||||
function tPrint(tbl, indent)
|
||||
indent = indent or 0
|
||||
for k, v in pairs(tbl) do
|
||||
local tblType = type(v)
|
||||
local formatting = string.rep(" ", indent) .. k .. ": "
|
||||
|
||||
if tblType == "table" then
|
||||
print(formatting)
|
||||
tPrint(v, indent + 1)
|
||||
elseif tblType == 'boolean' then
|
||||
print(formatting .. tostring(v))
|
||||
elseif tblType == "function" then
|
||||
print(formatting .. tostring(v))
|
||||
else
|
||||
print(formatting .. v)
|
||||
end
|
||||
end
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
1
resources/[voice]/pma-voice/ui/css/app.css
Normal file
1
resources/[voice]/pma-voice/ui/css/app.css
Normal file
|
|
@ -0,0 +1 @@
|
|||
.voiceInfo{font-family:Avenir,Helvetica,Arial,sans-serif;position:fixed;text-align:right;bottom:5px;padding:0;right:5px;font-size:12px;font-weight:700;color:#949697;text-shadow:1.25px 0 0 #000,0 -1.25px 0 #000,0 1.25px 0 #000,-1.25px 0 0 #000}.talking{color:hsla(0,0%,100%,.822)}p{margin:0}
|
||||
1
resources/[voice]/pma-voice/ui/index.html
Normal file
1
resources/[voice]/pma-voice/ui/index.html
Normal file
File diff suppressed because one or more lines are too long
2
resources/[voice]/pma-voice/ui/js/app.js
Normal file
2
resources/[voice]/pma-voice/ui/js/app.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/[voice]/pma-voice/ui/js/app.js.map
Normal file
1
resources/[voice]/pma-voice/ui/js/app.js.map
Normal file
File diff suppressed because one or more lines are too long
2
resources/[voice]/pma-voice/ui/js/chunk-vendors.js
Normal file
2
resources/[voice]/pma-voice/ui/js/chunk-vendors.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/[voice]/pma-voice/ui/js/chunk-vendors.js.map
Normal file
1
resources/[voice]/pma-voice/ui/js/chunk-vendors.js.map
Normal file
File diff suppressed because one or more lines are too long
BIN
resources/[voice]/pma-voice/ui/mic_click_off.ogg
Normal file
BIN
resources/[voice]/pma-voice/ui/mic_click_off.ogg
Normal file
Binary file not shown.
BIN
resources/[voice]/pma-voice/ui/mic_click_on.ogg
Normal file
BIN
resources/[voice]/pma-voice/ui/mic_click_on.ogg
Normal file
Binary file not shown.
3
resources/[voice]/pma-voice/voice-ui/.browserslistrc
Normal file
3
resources/[voice]/pma-voice/voice-ui/.browserslistrc
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
23
resources/[voice]/pma-voice/voice-ui/.gitignore
vendored
Normal file
23
resources/[voice]/pma-voice/voice-ui/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
24
resources/[voice]/pma-voice/voice-ui/README.md
Normal file
24
resources/[voice]/pma-voice/voice-ui/README.md
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
# voice-ui
|
||||
|
||||
## Project setup
|
||||
```
|
||||
yarn install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
yarn serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
yarn build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
5
resources/[voice]/pma-voice/voice-ui/babel.config.js
Normal file
5
resources/[voice]/pma-voice/voice-ui/babel.config.js
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
||||
18
resources/[voice]/pma-voice/voice-ui/package.json
Normal file
18
resources/[voice]/pma-voice/voice-ui/package.json
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "voice-ui",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"vue": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0"
|
||||
}
|
||||
}
|
||||
6692
resources/[voice]/pma-voice/voice-ui/pnpm-lock.yaml
generated
Normal file
6692
resources/[voice]/pma-voice/voice-ui/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load diff
17
resources/[voice]/pma-voice/voice-ui/public/index.html
Normal file
17
resources/[voice]/pma-voice/voice-ui/public/index.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
BIN
resources/[voice]/pma-voice/voice-ui/public/mic_click_off.ogg
Normal file
BIN
resources/[voice]/pma-voice/voice-ui/public/mic_click_off.ogg
Normal file
Binary file not shown.
BIN
resources/[voice]/pma-voice/voice-ui/public/mic_click_on.ogg
Normal file
BIN
resources/[voice]/pma-voice/voice-ui/public/mic_click_on.ogg
Normal file
Binary file not shown.
112
resources/[voice]/pma-voice/voice-ui/src/App.vue
Normal file
112
resources/[voice]/pma-voice/voice-ui/src/App.vue
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
<template>
|
||||
<body>
|
||||
<audio id="audio_on" src="mic_click_on.ogg"></audio>
|
||||
<audio id="audio_off" src="mic_click_off.ogg"></audio>
|
||||
<div v-if="voice.uiEnabled" class="voiceInfo">
|
||||
<p v-if="voice.callInfo !== 0" :class="{ talking: voice.talking }">
|
||||
[Call]
|
||||
</p>
|
||||
<p v-if="voice.radioEnabled && voice.radioChannel !== 0" :class="{ talking: voice.usingRadio }">
|
||||
{{ voice.radioChannel }} Mhz [Radio]
|
||||
</p>
|
||||
<p v-if="voice.voiceModes.length" :class="{ talking: voice.talking }">
|
||||
{{ voice.voiceModes[voice.voiceMode][1] }} [Range]
|
||||
</p>
|
||||
</div>
|
||||
</body>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { reactive } from "vue";
|
||||
export default {
|
||||
name: "App",
|
||||
setup() {
|
||||
const voice = reactive({
|
||||
uiEnabled: true,
|
||||
voiceModes: [],
|
||||
voiceMode: 0,
|
||||
radioChannel: 0,
|
||||
radioEnabled: true,
|
||||
usingRadio: false,
|
||||
callInfo: 0,
|
||||
talking: false,
|
||||
});
|
||||
|
||||
// stops from toggling voice at the end of talking
|
||||
window.addEventListener("message", function(event) {
|
||||
const data = event.data;
|
||||
|
||||
if (data.uiEnabled !== undefined) {
|
||||
voice.uiEnabled = data.uiEnabled
|
||||
}
|
||||
|
||||
if (data.voiceModes !== undefined) {
|
||||
voice.voiceModes = JSON.parse(data.voiceModes);
|
||||
// Push our own custom type for modes that have their range changed
|
||||
let voiceModes = [...voice.voiceModes]
|
||||
voiceModes.push([0.0, "Custom"])
|
||||
voice.voiceModes = voiceModes
|
||||
}
|
||||
|
||||
if (data.voiceMode !== undefined) {
|
||||
voice.voiceMode = data.voiceMode;
|
||||
}
|
||||
|
||||
if (data.radioChannel !== undefined) {
|
||||
voice.radioChannel = data.radioChannel;
|
||||
}
|
||||
|
||||
if (data.radioEnabled !== undefined) {
|
||||
voice.radioEnabled = data.radioEnabled;
|
||||
}
|
||||
|
||||
if (data.callInfo !== undefined) {
|
||||
voice.callInfo = data.callInfo;
|
||||
}
|
||||
|
||||
if (data.usingRadio !== undefined && data.usingRadio !== voice.usingRadio) {
|
||||
voice.usingRadio = data.usingRadio;
|
||||
}
|
||||
|
||||
if ((data.talking !== undefined) && !voice.usingRadio) {
|
||||
voice.talking = data.talking;
|
||||
}
|
||||
|
||||
if (data.sound && voice.radioEnabled && voice.radioChannel !== 0) {
|
||||
let click = document.getElementById(data.sound);
|
||||
// discard these errors as its usually just a 'uncaught promise' from two clicks happening too fast.
|
||||
click.load();
|
||||
click.volume = data.volume;
|
||||
click.play().catch((e) => {});
|
||||
}
|
||||
});
|
||||
|
||||
fetch(`https://${GetParentResourceName()}/uiReady`, { method: 'POST' });
|
||||
|
||||
return { voice };
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.voiceInfo {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
position: fixed;
|
||||
text-align: right;
|
||||
bottom: 5px;
|
||||
padding: 0;
|
||||
right: 5px;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: rgb(148, 150, 151);
|
||||
/* https://stackoverflow.com/questions/4772906/css-is-it-possible-to-add-a-black-outline-around-each-character-in-text */
|
||||
text-shadow: 1.25px 0 0 #000, 0 -1.25px 0 #000, 0 1.25px 0 #000,
|
||||
-1.25px 0 0 #000;
|
||||
}
|
||||
.talking {
|
||||
color: rgba(255, 255, 255, 0.822);
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
4
resources/[voice]/pma-voice/voice-ui/src/main.js
Normal file
4
resources/[voice]/pma-voice/voice-ui/src/main.js
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
createApp(App).mount('#app')
|
||||
7
resources/[voice]/pma-voice/voice-ui/vue.config.js
Normal file
7
resources/[voice]/pma-voice/voice-ui/vue.config.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
module.exports = {
|
||||
publicPath: './',
|
||||
productionSourceMap: true,
|
||||
filenameHashing: false,
|
||||
outputDir: "../ui",
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue