├── .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 |
--------------------------------------------------------------------------------