├── .vscode └── settings.json ├── fxmanifest.lua ├── server_config.lua ├── client_config.lua ├── README.md ├── server.lua └── client.lua /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Lua.diagnostics.globals": [ 3 | "lib", 4 | "cache" 5 | ], 6 | "Lua.diagnostics.disable": [ 7 | "lowercase-global" 8 | ] 9 | } -------------------------------------------------------------------------------- /fxmanifest.lua: -------------------------------------------------------------------------------- 1 | fx_version 'cerulean' 2 | game 'gta5' 3 | 4 | shared_script '@ox_lib/init.lua' 5 | lua54 'yes' 6 | 7 | client_scripts { 8 | 'client_config.lua', 9 | 'client.lua', 10 | } 11 | 12 | server_scripts { 13 | 'server_config.lua', 14 | 'server.lua', 15 | } 16 | 17 | dependency 'ox_lib' 18 | 19 | author 'Grav' 20 | version '1.6.3' 21 | description 'Engine sound menu that syncs to other players' -------------------------------------------------------------------------------- /server_config.lua: -------------------------------------------------------------------------------- 1 | Config = { 2 | CheckForUpdates = true, -- will check github for updates (recommended) 3 | HasPermission = function(src) 4 | -- your permission function here - you can integrate your framework for jobs/perms etc 5 | return IsPlayerAceAllowed(src, 'enginesoundmenu') 6 | end, 7 | Notify = function(src, msg, type) 8 | -- you can edit this to whatever you want, by default it uses ox_lib notifications 9 | TriggerClientEvent("ox_lib:notify", src, { 10 | description = msg, 11 | type = type, 12 | position = 'center-right', 13 | duration = 6500, 14 | }) 15 | end, 16 | BanPlayer = function(src) 17 | -- your ban function here 18 | print(string.format("%s [%s] has been banned for exploiting events!", GetPlayerName(src), src)) 19 | DropPlayer(src, 'Exploiting Events!') 20 | end, 21 | } 22 | -------------------------------------------------------------------------------- /client_config.lua: -------------------------------------------------------------------------------- 1 | Config = { 2 | Keybind = "", -- E.G F7 ---> https://docs.fivem.net/docs/game-references/controls/ 3 | MenuPosition = "bottom-right", -- bottom-right, bottom-left, top-right, top-left 4 | StoreSoundsByModel = true, -- This option only applies as a default setting, do you want to (by default) store engine sounds by the model (spawncode) 5 | Notify = function(msg, type) 6 | -- customise this notification function to whatever you desire - by default it uses ox_lib but you can edit this 7 | lib.notify( 8 | { 9 | description = msg, 10 | type = type, 11 | position = "center-right", 12 | duration = 6500, 13 | } 14 | ) 15 | end, 16 | EngineSounds = { 17 | -- Engine Sound Name/Label --> Hash of engine audio (what you'd normally put in vehicles.meta) 18 | ["Baller"] = "baller", 19 | ["Adder"] = "adder", 20 | ["Lazer"] = "lazer" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # Engine Sound Menu - FiveM 4 | Easy to use menu to allow you to change your engine sound ingame - syncs to all clients via statebags. 5 | 6 | If you require support or need more information, please [visit our docs](https://docs.grav.wtf/docs/enginesoundmenu/information) 7 | 8 | ## Dependencies 9 | [ox_lib](https://github.com/overextended/ox_lib) 10 | 11 | The default permission ace for this command is `enginesoundmenu` - you can give this to a group such as: 12 | `add_ace group.donator enginesoundmenu allow` 13 | 14 | If you want to edit the permissions check such as for jobs or your framework, edit the `server_config.lua` 15 | You can configure your sounds via `client_config.lua` as well as the default keybind - leave it blank if no default keybind is wanted. 16 | 17 | This menu also supports favourites! Quickly find the best engine sounds and shortlist them for convenience! 18 | Toggleable option to automatically save what models you use specific sounds on that will be re-applied when you re-enter or respawn the vehicle. 19 | -------------------------------------------------------------------------------- /server.lua: -------------------------------------------------------------------------------- 1 | local format = string.format 2 | 3 | RegisterCommand("enginesound", function(source, args, rawCommand) 4 | if not Config.HasPermission(source) then 5 | Config.Notify(source, 'You do not have permission to use this command!', 'error') 6 | return 7 | end 8 | TriggerClientEvent("GLabs:EngineSounds:OpenMenu", source) 9 | end, false) 10 | 11 | RegisterServerEvent("GLabs:EngineSounds:ChangeEngineSound", function(data) 12 | local src = source 13 | 14 | if not Config.HasPermission(src) then 15 | -- this is to prevent again any potential exploiters triggering your events 16 | return Config.BanPlayer(src) 17 | end 18 | 19 | if type(data) ~= "table" then 20 | lib.print.error(format("[changeEngineSound] %s [%s] sent invalid data to the server.", GetPlayerName(src), src)) 21 | return 22 | end 23 | 24 | local entity = NetworkGetEntityFromNetworkId(data.net) 25 | if not DoesEntityExist(entity) then return end 26 | Entity(entity).state['vehdata:sound'] = data.sound 27 | 28 | lib.print.debug(format("%s [%s] has changed their engine sound to %s", GetPlayerName(src), src, data.label)) 29 | end) 30 | 31 | lib.callback.register("GLabs:EngineSounds:GetPerms", function(source) 32 | return Config.HasPermission(source) 33 | end) 34 | 35 | -- Version Checking 36 | CreateThread(function() 37 | if not Config.CheckForUpdates then 38 | print( 39 | "\n^6GLabs_EngineSoundMenu ^2has been loaded - enjoy! ^1[VERSION CHECK DISABLED]\n^2For support or issues, please visit ^3https://discord.grav.wtf^7") 40 | return 41 | end 42 | 43 | lib.versionCheck('Gravxd/enginesound-menu') 44 | 45 | print(string.format("^6GLabs_EngineSoundMenu ^2has been loaded - enjoy!\n^2For support or issues, please visit ^3https://discord.grav.wtf^7")) 46 | 47 | end) 48 | -------------------------------------------------------------------------------- /client.lua: -------------------------------------------------------------------------------- 1 | local DisplayLabels = {} 2 | Config.EngineSounds["Restore Default"] = "resetenginesound" -- hash is invalid so game resets to base sound 3 | for k, _ in pairs(Config.EngineSounds) do 4 | DisplayLabels[#DisplayLabels + 1] = k 5 | end 6 | 7 | local format = string.format 8 | local GetEntityModel = GetEntityModel 9 | 10 | local storeSoundsForModel = GetResourceKvpInt("storeSoundsForModel") ~= 0 and true or Config.StoreSoundsByModel 11 | lib.print.debug("Store sounds for model: ", storeSoundsForModel) 12 | 13 | local hasPermissions = lib.callback.await("GLabs:EngineSounds:GetPerms", false) 14 | 15 | local Index = 1 16 | local Favourites = {} 17 | 18 | local function loadFavourites() 19 | local jsonFavourites = GetResourceKvpString("favouriteEngineSounds") 20 | if jsonFavourites then 21 | Favourites = json.decode(jsonFavourites) 22 | end 23 | end 24 | 25 | local function saveFavourites() 26 | SetResourceKvp("favouriteEngineSounds", json.encode(Favourites)) 27 | end 28 | 29 | loadFavourites() 30 | 31 | local function showEngineSoundMenu() 32 | lib.setMenuOptions( 33 | "engine_sound_menu", 34 | { 35 | label = "Change Engine Sound", 36 | icon = "arrows-up-down-left-right", 37 | values = DisplayLabels, 38 | defaultIndex = Index, 39 | close = false 40 | }, 41 | 1 42 | ) 43 | lib.setMenuOptions("engine_sound_menu", 44 | { label = "Store Sounds Per Model", description = 45 | "If you use an engine sound on a vehicle it will save, if you spawn this vehicle again, it will restore that last used engine sound.", checked = 46 | storeSoundsForModel, icon = "fa-solid fa-floppy-disk" }, 4) 47 | lib.showMenu("engine_sound_menu") 48 | end 49 | 50 | local function showRemoveFavouritesMenu() 51 | local removeOptions = {} 52 | for fav, _ in pairs(Favourites) do 53 | removeOptions[#removeOptions + 1] = { label = fav, icon = 'trash' } 54 | end 55 | 56 | lib.registerMenu( 57 | { 58 | id = "remove_favourites_menu", 59 | title = "Remove from Favourites", 60 | position = Config.MenuPosition, 61 | options = removeOptions, 62 | onClose = function() 63 | showFavouritesMenu() 64 | end 65 | }, 66 | function(selected) 67 | local soundToRemove = removeOptions[selected].label 68 | Favourites[soundToRemove] = nil 69 | saveFavourites() 70 | Config.Notify("Engine sound removed from favourites!", "success") 71 | if next(Favourites) == nil then 72 | Config.Notify("You have no more favourites!", "info") 73 | showEngineSoundMenu() 74 | else 75 | showRemoveFavouritesMenu() 76 | end 77 | end 78 | ) 79 | 80 | lib.showMenu("remove_favourites_menu") 81 | end 82 | 83 | local function IsRestricted() 84 | if LocalPlayer.state.dead then 85 | return true 86 | end 87 | return false 88 | end 89 | 90 | function showFavouritesMenu() 91 | if next(Favourites) == nil then 92 | Config.Notify("You don't have any favourites!", "error") 93 | return 94 | end 95 | 96 | local favouriteOptions = {} 97 | for fav, _ in pairs(Favourites) do 98 | favouriteOptions[#favouriteOptions + 1] = { label = fav, icon = 'star' } 99 | end 100 | favouriteOptions[#favouriteOptions + 1] = { label = "Remove from Favourites", icon = "trash" } 101 | 102 | lib.registerMenu( 103 | { 104 | id = "favourites_menu", 105 | title = "Favourites", 106 | position = Config.MenuPosition, 107 | options = favouriteOptions, 108 | onClose = function() 109 | showEngineSoundMenu() 110 | end 111 | }, 112 | function(selected) 113 | local selectedFav = favouriteOptions[selected].label 114 | if selectedFav == "Remove from Favourites" then 115 | showRemoveFavouritesMenu() 116 | else 117 | if not cache.vehicle or cache.seat ~= -1 then 118 | return Config.Notify("You need to be driving a vehicle to use this!", "error") 119 | end 120 | 121 | if IsRestricted() then 122 | return Config.Notify("You aren't able to use this right now!", "error") 123 | end 124 | 125 | local success = changeSoundForVehicle(cache.vehicle, Config.EngineSounds[selectedFav], selectedFav) 126 | if not success then return end 127 | 128 | Config.Notify(string.format("Engine sound changed to: %s", selectedFav), "success") 129 | lib.showMenu("favourites_menu") 130 | end 131 | end 132 | ) 133 | 134 | lib.showMenu("favourites_menu") 135 | end 136 | 137 | function changeSoundForVehicle(vehicle, sound, label) 138 | if IsVehicleSirenOn(vehicle) then 139 | -- weird bug with LVC that caused sirens to emit noise whilst lights disabled 140 | Config.Notify("You can't change the engine sound while the siren is on!", "error") 141 | return false 142 | end 143 | 144 | TriggerServerEvent( 145 | "GLabs:EngineSounds:ChangeEngineSound", 146 | { 147 | net = VehToNet(vehicle), 148 | sound = sound, 149 | label = label 150 | } 151 | ) 152 | return true 153 | end 154 | 155 | local storedModelSounds = GetResourceKvpString("storedModelSounds") and 156 | json.decode(GetResourceKvpString("storedModelSounds")) or {} 157 | 158 | lib.onCache("vehicle", function(value) 159 | if not hasPermissions then return end 160 | if not value then return end 161 | if not storeSoundsForModel then return end 162 | local driverPed = GetPedInVehicleSeat(value, -1) 163 | if driverPed ~= cache.ped then 164 | lib.print.debug("I am not driver, do not set engine sound.") 165 | return 166 | end 167 | local vehicleModel = GetEntityModel(value) 168 | lib.print.debug(format("[vehicleChange] Model: %s - %s - %s", vehicleModel, storedModelSounds[vehicleModel], 169 | storedModelSounds[tostring(vehicleModel)])) 170 | local savedModelSound = storedModelSounds[tostring(vehicleModel)] 171 | if savedModelSound then 172 | lib.print.debug(format("Found sound for model %s: %s", vehicleModel, savedModelSound.label)) 173 | changeSoundForVehicle(value, savedModelSound.sound, savedModelSound.label) 174 | end 175 | end) 176 | 177 | lib.registerMenu( 178 | { 179 | id = "engine_sound_menu", 180 | title = "Engine Sound Menu", 181 | position = Config.MenuPosition, 182 | onSideScroll = function(_, scrollIndex) 183 | Index = scrollIndex 184 | end, 185 | onCheck = function(selected, checked, args) 186 | if selected == 4 then 187 | -- store sounds on models 188 | storeSoundsForModel = checked 189 | SetResourceKvpInt("storeSoundsForModel", storeSoundsForModel and 1 or 0) 190 | if storeSoundsForModel then 191 | Config.Notify("Engine sounds will now be stored per model!", "success") 192 | else 193 | Config.Notify("Engine sounds will no longer be stored per model!", "error") 194 | end 195 | end 196 | end, 197 | options = { 198 | { label = "Change Engine Sound", icon = "arrows-up-down-left-right", values = DisplayLabels }, 199 | { label = "Add to Favourites", icon = "heart" }, 200 | { label = "View Favourites", icon = "star" }, 201 | { label = "Store Sounds Per Model", description = "If you use an engine sound on a vehicle it will save, if you spawn this vehicle again, it will restore that last used engine sound.", checked = storeSoundsForModel, icon = "fa-solid fa-floppy-disk" } 202 | } 203 | }, 204 | function(selected, scrollIndex) 205 | if selected == 1 then 206 | -- change engine sound 207 | if not cache.vehicle or cache.seat ~= -1 then 208 | return Config.Notify("You need to be driving a vehicle to use this!", "error") 209 | end 210 | 211 | if IsRestricted() then 212 | return Config.Notify("You aren't able to use this right now!", "error") 213 | end 214 | 215 | local success = changeSoundForVehicle(cache.vehicle, Config.EngineSounds[DisplayLabels[scrollIndex]], 216 | DisplayLabels[scrollIndex]) 217 | if not success then return end 218 | 219 | if storeSoundsForModel then 220 | local vehicleModel = GetEntityModel(cache.vehicle) 221 | 222 | if DisplayLabels[scrollIndex] == "Restore Default" then 223 | storedModelSounds[tostring(vehicleModel)] = nil 224 | else 225 | storedModelSounds[tostring(vehicleModel)] = { 226 | sound = Config.EngineSounds[DisplayLabels[scrollIndex]], 227 | label = DisplayLabels[scrollIndex] 228 | } 229 | end 230 | 231 | SetResourceKvp("storedModelSounds", json.encode(storedModelSounds)) 232 | lib.print.debug(format("Updated sound for model %s: %s", vehicleModel, DisplayLabels[scrollIndex])) 233 | end 234 | 235 | Config.Notify(string.format("Engine sound changed to: %s", DisplayLabels[scrollIndex]), "success") 236 | elseif selected == 2 then 237 | -- add to favourites 238 | local soundName = DisplayLabels[Index] 239 | 240 | if soundName == "Restore Default" then 241 | Config.Notify("You can't favourite the default engine sound!", "error") 242 | return 243 | end 244 | 245 | if not Favourites[soundName] then 246 | Favourites[soundName] = true 247 | saveFavourites() 248 | Config.Notify("Engine sound added to favourites!", "success") 249 | else 250 | Config.Notify("This engine sound is already in your favourites!", "error") 251 | end 252 | showEngineSoundMenu() 253 | elseif selected == 3 then 254 | -- view favourites 255 | showFavouritesMenu() 256 | end 257 | end 258 | ) 259 | 260 | RegisterNetEvent( 261 | "GLabs:EngineSounds:OpenMenu", 262 | function() 263 | hasPermissions = true -- incase they get permissions added after startup 264 | if not cache.vehicle or cache.seat ~= -1 then 265 | return Config.Notify("You need to be driving a vehicle to use this!", "error") 266 | end 267 | showEngineSoundMenu() 268 | end 269 | ) 270 | 271 | AddStateBagChangeHandler( 272 | "vehdata:sound", 273 | nil, 274 | function(bagName, _, value) 275 | local entity = GetEntityFromStateBagName(bagName) 276 | if entity == 0 then return end 277 | if not IsEntityAVehicle(entity) then return end 278 | ForceUseAudioGameObject(entity, value) 279 | end 280 | ) 281 | 282 | lib.addKeybind( 283 | { 284 | name = "open_enginesound_menu", 285 | description = "Open Engine Sound Menu", 286 | defaultKey = Config.Keybind, 287 | onPressed = function() 288 | ExecuteCommand("enginesound") 289 | end 290 | } 291 | ) 292 | 293 | TriggerEvent("chat:addSuggestion", "/enginesound", "Open the Engine Sound Menu!") 294 | --------------------------------------------------------------------------------