1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-07 14:38:30 +02:00
parent 88ffeb3375
commit 87623c8379
27 changed files with 2865 additions and 1705 deletions

Binary file not shown.

View file

@ -0,0 +1,174 @@
Custom license
Copyright (c) since 2024 by Philipp Decker
Last updated: July 27, 2025
Contact for business enquiries: kiminaze@yahoo.de
"AdvancedParking" (the "software") is a resource for the "FiveM" modification for the game "Grand
Theft Auto V" and is comprised of the files this license is attached to. It is only meant to
function inside of this environment.
The software is made legally available through the online store located at https://tbx.kiminaze.de
and can be downloaded at https://portal.cfx.re/assets/granted-assets after purchase.
The information in this license can be changed by any update of the software.
The "customer" (the person that acquired this software legally) is granted permission to use and
modify all of its parts except the encrypted portions of the software.
The customer is not allowed to publish and/or distribute the software, copies of the software,
parts of the software and/or modifications of the software. However - for the software to function
properly within the environment - distribution of the necessary client side files via the FiveM
server is permitted, provided these files have been legally acquired.
By using this software the customer is accepting this license and by updating the software any
potential changes are accepted as well.
## Updates
Updates to the software are provided by the copyright holder given there are new features to be
implemented into the software or problems with the software (errors, bugs, glitches) that need to
be resolved.
Updates will only be provided as deemed necessary by the copyright holder.
Updates can be downloaded at https://portal.cfx.re/assets/granted-assets.
Major updates can potentially contain breaking changes towards backwards compatibility that will be
communicated in the patch notes. It is the customers responsibility to carefully read the patch
notes.
Patch notes for outdated software can be found in the FiveM server console log when starting the
software. Notifications for new updates can be seen in the official Discord server and as new posts
in the official FiveM forum.
All patch notes are located at https://github.com/Kiminaze/version_history.
## Support
Support for the software is provided at the [official Discord server](https://discord.kiminaze.de)
in English and German.
Support is provided to the customer and a second person ("developer") that can be chosen at the
customers discretion.
Support is provided for, but not limited to:
- Installation of the software
- Configuration of the software
- Problems revolving directly around the software (errors, bugs, glitches)
Support can be provided in the following forms:
- text chat
- voice call
- voice call with screenshare
Support for the software will be terminated under the following conditions:
- The online store closes.
- The software is no longer being sold.
The customer can be excluded from support when infringing upon this license or breaking any rule in
the Discord server or in Discord in general. Not providing necessary information after repeated
requests can also lead to exclusion. Should this happen and a developer had been chosen for
support, they will also be excluded from support.
If the customer has been excluded from support, support can be reinstated by resolving any
previously arisen issues.
All liability claims no longer apply if changes have been made to the software by the customer.
## Limitation of Liability
The copyright holder is not liable for any damages arising from modifying the software given its
accessibility of the code.
If the software has been obtained from a non-legal source, the copyright
holder is no longer liable for any damages from using the software.
## Supremacy of Platform License Agreement
This License Agreement is subordinate to and does not replace or override the original Creator
Platform License Agreement ("PLA") set in place by Cfx (https://fivem.net/terms). In the event of
any conflict between this License Agreement and the PLA, the terms of the PLA shall prevail. All
users must comply with the terms and conditions of the PLA in addition to the terms set forth in
this License Agreement.
## Further information
Further information referring to the software can be found in the
[documentation](https://docs.kiminaze.de).
## Privacy Policy
### When is information collected?
Data is collected **only** when telemetry is **explicitly enabled** by the customer via the
[provided convar](https://docs.kiminaze.de/scripts/advancedparking/convars#telemetry). Telemetry
can be enabled or disabled at any time at the customer's discretion. Telemetry data is disabled
by default and opt-in.
Once enabled, telemetry data is sent in the following situations:
- When a defined number of log entries is reached,
- When this resource is stopped,
- When the server is about to be shut down,
- When a script error has occured,
- When a client disconnects from the server.
### What information is collected?
- Cfx (FiveM) identifier of the server owner
- Server build version
- Gamebuild version
- Version of this resource
- Timestamps of log entries
- Client logs produced by this resource
- Server logs produced by this resource
- Script errors generated by this resource
**Note:** No personal data such as names or IP addresses is collected apart from the aforementioned
Cfx identifier.
### Why is information collected?
Telemetry data is collected for the following purposes:
- Debugging issues related to this resource
- Providing technical support and troubleshooting
- Improving the stability and reliability of this resource
### Who has access to the data and how is it protected?
Data is transmitted via secure HTTPS requests and access to the data is strictly limited to the
copyright owner (Philipp Decker) and authorized support staff.
### Is data shared with third parties?
Telemetry data is generally not shared with third parties. In support situations, telemetry
data may be used to visualize problems to the customer (and their developer) and support tickets
are generally visible to everyone who has access to this resource's section in the
[official Discord server](https://discord.kiminaze.de).
### How long is data stored?
Collected data is stored for **a maximum of 8 days**, after which it is automatically and
permanently erased. Should the server owner disable the telemetry, collected data will be erased
after **a maximum of 8 days**.
### How can I get my data deleted?
To request immediate deletion of telemetry data, please contact support in the
[official Discord server](https://discord.kiminaze.de). Deletion requests are processed as soon as
possible.
### Who is responsible for GDPR compliance?
Philipp Decker - Videogame and Software Development (German small business)
Email: kiminaze@yahoo.de

View file

@ -0,0 +1,15 @@
## Support
If you require any form of support after buying this resource, the right place to ask is our
Discord Server: https://discord.kiminaze.de
## Documentation
https://docs.kiminaze.de/scripts/advancedparking
## Patchnotes
https://github.com/Kiminaze/version_history/blob/main/AdvancedParking.json

View file

@ -0,0 +1,33 @@
-- get vehicle fuel level
function GetFuelLevel(vehicle)
if (GetResourceState("LegacyFuel") == "started") then
return exports["LegacyFuel"]:GetFuel(vehicle)
elseif (GetResourceState("ox_fuel") == "started") then
return Entity(vehicle).state.fuel or GetVehicleFuelLevel(vehicle)
elseif (GetResourceState("myFuel") == "started") then
return exports["myFuel"]:GetFuel(vehicle)
else
return GetVehicleFuelLevel(vehicle)
end
end
-- set vehicle fuel level
function SetFuelLevel(vehicle, fuelLevel)
if (GetResourceState("LegacyFuel") == "started") then
exports["LegacyFuel"]:SetFuel(vehicle, fuelLevel)
elseif (GetResourceState("ox_fuel") == "started") then
Entity(vehicle).state.fuel = fuelLevel
elseif (GetResourceState("myFuel") == "started") then
exports["myFuel"]:SetFuel(vehicle, fuelLevel)
else
SetVehicleFuelLevel(vehicle, fuelLevel)
end
end
-- notification (only used for the delete timer)
function ShowNotification(text)
SetNotificationTextEntry('STRING')
AddTextComponentSubstringPlayerName(text)
return DrawNotification(false, true)
end

View file

@ -0,0 +1,475 @@
local math_min, math_max, math_floor = math.min, math.max, math.floor
-- returns all tuning of a vehicle
function GetVehicleTuning(vehicle)
-- main colors
local primaryColor, secondaryColor = GetVehicleColours(vehicle)
local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle)
-- custom colors
local customPrimaryColor, customSecondaryColor
if (GetIsVehiclePrimaryColourCustom(vehicle)) then
local r, g, b = GetVehicleCustomPrimaryColour(vehicle)
customPrimaryColor = { r, g, b }
end
if (GetIsVehicleSecondaryColourCustom(vehicle)) then
local r, g, b = GetVehicleCustomSecondaryColour(vehicle)
customSecondaryColor = { r, g, b }
end
-- tire smoke color
r, g, b = GetVehicleTyreSmokeColor(vehicle)
local tireSmokeColor = { r, g, b }
-- neon lights color
local r, g, b = GetVehicleNeonLightsColour(vehicle)
local neonLightsColor = { r, g, b }
local enabledNeonLights = {
IsVehicleNeonLightEnabled(vehicle, 0),
IsVehicleNeonLightEnabled(vehicle, 1),
IsVehicleNeonLightEnabled(vehicle, 2),
IsVehicleNeonLightEnabled(vehicle, 3),
}
return {
-- 1
GetVehicleNumberPlateText(vehicle),
-- 2
GetVehicleMods(vehicle),
-- 3
primaryColor,
-- 4
secondaryColor,
-- 5
pearlescentColor,
-- 6
wheelColor,
-- 7
customPrimaryColor,
-- 8
customSecondaryColor,
-- 9
GetVehicleInteriorColor(vehicle),
-- 10
GetVehicleDashboardColor(vehicle),
-- 11
tireSmokeColor,
-- 12
GetVehicleXenonLightsColour(vehicle),
-- 13
neonLightsColor,
-- 14
enabledNeonLights,
-- 15
GetVehicleExtras(vehicle),
-- 16
GetVehicleWheelType(vehicle),
-- 17
GetVehicleModVariation(vehicle, 23),
-- 18
GetVehicleModVariation(vehicle, 24),
-- 19
not GetVehicleTyresCanBurst(vehicle),
-- 20
(GetGameBuildNumber() >= 2372) and GetDriftTyresEnabled(vehicle),
-- 21
GetVehicleNumberPlateTextIndex(vehicle),
-- 22
GetVehicleWindowTint(vehicle),
-- 23
GetVehicleLivery(vehicle),
-- 24
GetVehicleRoofLivery(vehicle)
}
end
-- apply all vehicle tuning
function SetVehicleTuning(vehicle, tuning)
SetVehicleModKit(vehicle, 0)
-- 16 wheelType
SetVehicleWheelType(vehicle, tuning[16])
-- 2 mods
SetVehicleMods(vehicle, tuning[2], tuning[17], tuning[18])
-- 3-4 primary/secondaryColor
SetVehicleColours(vehicle, tuning[3], tuning[4])
-- 5-6 pearlescent/wheelColor
SetVehicleExtraColours(vehicle, tuning[5], tuning[6])
-- 7 customPrimaryColor
if (tuning[7]) then
SetVehicleCustomPrimaryColour(vehicle, tuning[7][1], tuning[7][2], tuning[7][3])
end
-- 8 customSecondaryColor
if (tuning[8]) then
SetVehicleCustomSecondaryColour(vehicle, tuning[8][1], tuning[8][2], tuning[8][3])
end
-- 9 interiorColor
SetVehicleInteriorColor(vehicle, tuning[9])
-- 10 dashboardColor
SetVehicleDashboardColor(vehicle, tuning[10])
-- 11 tireSmokeColor
SetVehicleTyreSmokeColor(vehicle, tuning[11][1], tuning[11][2], tuning[11][3])
-- 12 xenonLightsColor
SetVehicleXenonLightsColour(vehicle, tuning[12])
-- 13 neonLightsColor
SetVehicleNeonLightsColour(vehicle, tuning[13][1], tuning[13][2], tuning[13][3])
-- 14 enabledNeonLights
for i = 0, 3, 1 do
SetVehicleNeonLightEnabled(vehicle, i, tuning[14][i + 1])
end
-- 15 extras
SetVehicleExtras(vehicle, tuning[15])
-- 19 bulletproofTires
SetVehicleTyresCanBurst(vehicle, not tuning[19])
-- 20 driftTires
if (GetGameBuildNumber() >= 2372) then
SetDriftTyresEnabled(vehicle, tuning[20])
end
-- 1 numberPlateText
SetVehicleNumberPlateText(vehicle, tuning[1])
-- 21 numberPlateTextIndex
SetVehicleNumberPlateTextIndex(vehicle, tuning[21])
-- 22 windowTint
SetVehicleWindowTint(vehicle, tuning[22])
-- 23 livery
SetVehicleLivery(vehicle, tuning[23])
-- 24 roofLivery
SetVehicleRoofLivery(vehicle, tuning[24])
end
-- returns the status values of a vehicle
function GetVehicleStatus(vehicle)
return {
-- 1 entity health
math_max(math_min(GetEntityHealth(vehicle), 1000), 0),
-- 2 body health
math_floor(math_max(math_min(GetVehicleBodyHealth(vehicle), 1000.0), 0.0) * 10.0) / 10.0,
-- 3 engine health
math_floor(math_max(math_min(GetVehicleEngineHealth(vehicle), 1000.0), -4000.0) * 10.0) / 10.0,
-- 4 petrol tank health
math_floor(math_max(math_min(GetVehiclePetrolTankHealth(vehicle), 1000.0), -1000.0) * 10.0) / 10.0,
-- 5 dirt level
math_floor(GetVehicleDirtLevel(vehicle) * 10.0) / 10.0,
-- 6 fuel level
math_floor(GetFuelLevel(vehicle) * 10.0) / 10.0,
-- 7 lock status
GetVehicleDoorLockStatus(vehicle),
-- 8 tire states
GetVehicleTireStates(vehicle),
-- 9 door states
GetVehicleDoorStates(vehicle),
-- 10 window states
GetVehicleWindowStates(vehicle),
}
end
-- apply all vehicle status values
function SetVehicleStatus(vehicle, status)
-- 1 entity health
SetEntityHealth(vehicle, math_floor(status[1]))
-- 2 body health
SetVehicleBodyHealth(vehicle, status[2] * 1.0)
-- 3 engine health
SetVehicleEngineHealth(vehicle, status[3] * 1.0)
-- 4 petrol tank health
SetVehiclePetrolTankHealth(vehicle, status[4] * 1.0)
-- 5 dirt level
SetVehicleDirtLevel(vehicle, status[5] * 1.0)
-- 6 fuel level
SetFuelLevel(vehicle, status[6] * 1.0)
-- 7 lock status
SetVehicleDoorsLocked(vehicle, status[7])
-- 8 tire states
SetVehicleTireStates(vehicle, status[8])
-- 9 door states
SetVehicleDoorStates(vehicle, status[9])
-- 10 window states
SetVehicleWindowStates(vehicle, status[10])
end
-- get additional values
local function GetConvertibleRoofIsOpen(vehicle)
if (not IsVehicleAConvertible(vehicle)) then return false end
local state = GetConvertibleRoofState(vehicle)
return state == 1 or state == 2 or state == 6
end
local function GetLandingGearIsUp(vehicle)
if (not DoesVehicleHaveLandingGear(vehicle)) then return false end
local state = GetLandingGearState(vehicle)
return state == 1 or state == 4
end
local function GetVtolMode(vehicle, model)
if (not IsThisModelAPlane(model)) then return false end
return GetVehicleFlightNozzlePosition(vehicle)
end
local function GetStubWingsAreDeployed(vehicle, model)
if (model ~= `akula` and model ~= `annihilator`) then return false end
return AreHeliStubWingsDeployed(vehicle)
end
local function GetBoatAnchorState(vehicle, model)
if (not IsThisModelABoat(model)) then return false end
return IsBoatAnchored(vehicle)
end
function GetVehicleExtraValues(vehicle)
local model = GetEntityModel(vehicle)
return {
engineState = GetIsVehicleEngineRunning(vehicle) or nil,
roofState = GetConvertibleRoofIsOpen(vehicle) or nil,
--outriggers = AreOutriggerLegsDeployed(vehicle) or nil, -- no setter counterpart
landingGearUp = GetLandingGearIsUp(vehicle) or nil,
vtolMode = GetVtolMode(vehicle, model) or nil,
stubWings = GetStubWingsAreDeployed(vehicle, model) or nil,
boatAnchor = GetBoatAnchorState(vehicle, model) or nil,
-- hydraulics?
}
end
-- set additional values
function SetVehicleExtraValues(vehicle, values)
if (values.engineState) then
SetVehicleEngineOn(vehicle, true, true, false)
end
if (values.roofState) then
LowerConvertibleRoof(vehicle, true)
end
if (values.landingGearUp) then
ControlLandingGear(vehicle, 3)
end
if (values.vtolMode) then
SetVehicleFlightNozzlePositionImmediate(vehicle, values.vtolMode)
end
if (values.stubWings) then
SetDeployHeliStubWings(vehicle, true, false)
end
if (values.boatAnchor) then
SetBoatRemainsAnchoredWhilePlayerIsDriver(vehicle, true)
SetBoatAnchor(vehicle, true)
end
-- hydraulics?
end
-- returns all non-stock vehicle mods
function GetVehicleMods(vehicle)
local mods = {}
for i = 0, 49, 1 do
-- TODO check for 17, 19, 21 -- toggle or normal mods? -- currently not possible
if (i == 18 or i == 20 or i == 22) then
if (IsToggleModOn(vehicle, i)) then
mods[#mods + 1] = {
-- 1 index
i,
-- 2 isToggledOn
true
}
end
else
local modIndex = GetVehicleMod(vehicle, i)
if (modIndex ~= -1) then
mods[#mods + 1] = {
-- 1 index
i,
-- 2 modIndex
modIndex
}
end
end
end
return mods
end
-- apply all vehicle mods
function SetVehicleMods(vehicle, mods, customFrontWheels, customRearWheels)
for i = 1, #mods do
local id = mods[i][1]
-- TODO check for 17, 19, 21 -- toggle or normal mods? -- currently not possible
if (id == 18 or id == 20 or id == 22) then
ToggleVehicleMod(vehicle, id, mods[i][2])
else
SetVehicleMod(vehicle, id, mods[i][2], (id == 24) and customRearWheels or customFrontWheels)
end
end
end
-- returns all vehicle extras
function GetVehicleExtras(vehicle)
local extras = {}
for i = 0, 20 do
if (DoesExtraExist(vehicle, i)) then
if (IsVehicleExtraTurnedOn(vehicle, i)) then
extras[#extras + 1] = {
-- 1 index
i,
-- 2 isToggledOn
0
}
else
extras[#extras + 1] = {
-- 1 index
i,
-- 2 isToggledOn
1
}
end
end
end
return extras
end
-- apply all vehicle extras
function SetVehicleExtras(vehicle, extras)
for i = 1, #extras do
SetVehicleExtra(vehicle, extras[i][1], extras[i][2])
end
end
-- returns all tire states
function GetVehicleTireStates(vehicle)
local burstTires = {}
for i = 0, 5 do
if (IsVehicleTyreBurst(vehicle, i, true)) then
burstTires[#burstTires + 1] = {
-- 1 index
i,
-- 2 isBurst
true
}
elseif (IsVehicleTyreBurst(vehicle, i, false)) then
burstTires[#burstTires + 1] = {
-- 1 index
i,
-- 2 isBurst
false
}
end
end
return burstTires
end
-- apply all tire states
function SetVehicleTireStates(vehicle, tireStates)
for i = 1, #tireStates do
SetVehicleTyreBurst(vehicle, tireStates[i][1], tireStates[i][2], 1000.0)
end
end
-- returns all door states
function GetVehicleDoorStates(vehicle)
local doorStates = {}
for i = 0, 7 do
if (GetIsDoorValid(vehicle, i)) then
doorStates[#doorStates + 1] = {
-- 1 index
i,
-- 2 missing
IsVehicleDoorDamaged(vehicle, i),
-- 3 angle (unused, causes problems)
--GetVehicleDoorAngleRatio(vehicle, i)
}
end
end
return doorStates
end
-- apply all door states
function SetVehicleDoorStates(vehicle, doorStates)
for i = 1, #doorStates do
if (doorStates[i][2]) then
SetVehicleDoorBroken(vehicle, doorStates[i][1], true)
--elseif (doorStates[i][3] > 0.0) then
-- SetVehicleDoorControl(vehicle, doorStates[i][1], 1000, doorStates[i][3])
end
end
end
-- returns all window states
function GetVehicleWindowStates(vehicle)
if (AreAllVehicleWindowsIntact(vehicle)) then
return {}
end
local windowStates = {}
for i = 0, 13 do
if (not IsVehicleWindowIntact(vehicle, i)) then
windowStates[#windowStates + 1] = i
end
end
return windowStates
end
-- apply all window states
function SetVehicleWindowStates(vehicle, windowStates)
for i = 1, #windowStates do
SmashVehicleWindow(vehicle, windowStates[i])
end
end
-- returns true if vehicle is blacklisted and should not be saved
function IsVehicleBlacklisted(vehicle)
-- check class
local class = GetVehicleClass(vehicle)
for i = 1, #classesBlacklist do
if (class == classesBlacklist[i]) then
return true
end
end
-- check model
local modelHash = GetEntityModel(vehicle)
for i = 1, #vehiclesBlacklist do
if (modelHash == vehiclesBlacklist[i]) then
return true
end
end
-- check plate
local plate = GetVehicleNumberPlateText(vehicle)
for i = 1, #platesBlacklist do
if (plate:find(platesBlacklist[i]:upper())) then
return true
end
end
return false
end

View file

@ -0,0 +1,193 @@
-- script status on startup
local enabled = true
-- update vehicle on server side
local function UpdateVehicle(vehicle, reason)
local tuning = GetVehicleTuning(vehicle)
LogDebug("Updating vehicle \"%s\" (%s)", tuning[1], reason)
TriggerServerEvent(
"AP:updateVehicle",
NetworkGetNetworkIdFromEntity(vehicle),
GetEntityModel(vehicle),
tuning,
GetVehicleStatus(vehicle),
GetVehicleExtraValues(vehicle),
reason
)
end
exports("UpdateVehicle", function(vehicle)
if (not DoesEntityExist(vehicle) or not NetworkGetEntityIsNetworked(vehicle)) then return end
UpdateVehicle(vehicle, "Resource \"" .. GetInvokingResource() .. "\"")
end)
-- when a player entered a vehicle
local function EnteredVehicle(vehicle)
if (not DoesEntityExist(vehicle) or not NetworkGetEntityIsNetworked(vehicle) or IsVehicleBlacklisted(vehicle)) then return end
if (GetVehicleTypeRaw(vehicle) == 2) then
TriggerServerEvent("AP:enteredTrailer", NetworkGetNetworkIdFromEntity(vehicle))
end
UpdateVehicle(vehicle, "Player enter")
end
-- when a player left a vehicle
local function LeftVehicle(vehicle)
if (not DoesEntityExist(vehicle) or not NetworkGetEntityIsNetworked(vehicle) or IsVehicleBlacklisted(vehicle)) then return end
if (GetVehicleTypeRaw(vehicle) == 2) then
TriggerServerEvent("AP:leftTrailer", NetworkGetNetworkIdFromEntity(vehicle))
end
UpdateVehicle(vehicle, "Player exit")
end
-- localising loop natives
local IsPedInAnyVehicle, GetVehiclePedIsIn, GetVehicleTrailerVehicle = IsPedInAnyVehicle, GetVehiclePedIsIn, GetVehicleTrailerVehicle
local isInVehicle = false
AddEventHandler("gameEventTriggered", function(eventName, eventData)
if (not enabled or isInVehicle or eventName ~= "CEventNetworkPlayerEnteredVehicle") then return end
-- entered vehicle
isInVehicle = true
local playerPed = PlayerPedId()
local vehicle = eventData[2]
local trailer = nil
EnteredVehicle(vehicle)
while (IsPedInAnyVehicle(playerPed)) do
-- check for instant vehicle switch
local newVehicle = GetVehiclePedIsIn(playerPed)
if (vehicle ~= newVehicle) then
LeftVehicle(vehicle)
EnteredVehicle(newVehicle)
vehicle = newVehicle
end
-- check for trailer
local hasTrailer, newTrailer = GetVehicleTrailerVehicle(vehicle)
if (hasTrailer) then
if (trailer ~= newTrailer) then
LeftVehicle(trailer)
EnteredVehicle(newTrailer)
end
trailer = newTrailer
elseif (trailer) then
LeftVehicle(trailer)
trailer = nil
end
Wait(0)
end
-- left vehicle
LeftVehicle(vehicle)
if (trailer) then
LeftVehicle(trailer)
end
isInVehicle = false
end)
-- setting tuning
AddStateBagChangeHandler("ap_data", nil, function(bagName, key, value, _unused, replicated)
if (bagName:find("entity") == nil or value == nil) then return end
local networkIdString = bagName:gsub("entity:", "")
local networkId = tonumber(networkIdString)
if (not WaitUntilEntityWithNetworkIdExists(networkId, 5000)) then return end
local vehicle = NetworkGetEntityFromNetworkId(networkId)
if (not WaitUntilPlayerEqualsEntityOwner(vehicle, 5000)) then return end
if (Cleanup.submergedVehicles and IsEntityInWater(vehicle) and GetEntitySubmergedLevel(vehicle) >= 0.5) then
local vehicleType = GetVehicleTypeRaw(vehicle)
if (vehicleType ~= 5 and vehicleType ~= 15) then
LogDebug("\"%s\" is submerged and will be deleted.", value[1][1])
DeleteSubmergedVehicle(vehicle)
return
end
end
LogDebug("Setting properties for \"%s\"", value[1][1])
SetVehicleTuning(vehicle, value[1])
SetVehicleStatus(vehicle, value[2])
SetVehicleExtraValues(vehicle, value[3])
SetEntityCoordsNoOffset(vehicle, value[4].x, value[4].y, value[4].z, false, false, true)
SetEntityRotation(vehicle, value[5].x, value[5].y, value[5].z)
TriggerEvent("AP:vehicleSpawned", vehicle)
end)
-- notification for delete timer
RegisterNetEvent("AP:showNotification", function(text)
ShowNotification(text)
end)
-- enables/disables the client sending enter/left events
local function Enable(enable)
assert(enable ~= nil and type(enable) == "boolean", "Parameter \"enable\" must be a bool!")
enabled = enable
end
exports("Enable", Enable)
-- export for force freezing vehicles
if (forceUnfreezeVehicles) then
local function FreezeVehicle(vehicle, freeze)
TriggerServerEvent("AP:freezeVehicle", NetworkGetNetworkIdFromEntity(vehicle), freeze)
end
exports("FreezeVehicle", FreezeVehicle)
end
local damageUpdates = {}
AddEventHandler("gameEventTriggered", function (name, args)
if (name ~= "CEventNetworkEntityDamage") then return end
local vehicle = args[1]
if (not DoesEntityExist(vehicle) or not IsEntityAVehicle(vehicle)) then return end
if (not NetworkGetEntityIsNetworked(vehicle)) then return end
local id = Entity(vehicle).state.ap_id
if (not id) then return end
local plate = GetVehicleNumberPlateText(vehicle)
LogDebug("Damage detected on vehicle \"%s\" (%s)", plate, id)
if (damageUpdates[vehicle]) then
damageUpdates[vehicle] = GetGameTimer() + 5000
return
end
damageUpdates[vehicle] = GetGameTimer() + 5000
while (damageUpdates[vehicle] > GetGameTimer()) do
Wait(0)
end
damageUpdates[vehicle] = nil
if (not DoesEntityExist(vehicle) or NetworkGetEntityOwner(vehicle) ~= PlayerId()) then return end
LogDebug("Sending damage update to server for vehicle \"%s\" (%s)", plate, id)
TriggerServerEvent("AP:onVehicleDamage", NetworkGetNetworkIdFromEntity(vehicle), GetVehicleStatus(vehicle))
end)

View file

@ -0,0 +1,118 @@
-- settings for the cleanup process
Cleanup = {
-- all vehicles will be removed that haven't had an update for X hours
-- set to nil to disable
timeThreshold = 24 * 7,
-- all vehicles with an engine health value equal to or below X will be removed
-- set to nil to disable
-- set to 0.0 for vehicles with a broken engine
-- set to -3999.0 for exploded Vehicles
engineThreshold = nil,
-- all vehicles further than X meters away from players will be removed
-- set to nil to disable
distanceThreshold = nil,
-- all submerged vehicles will be removed
submergedVehicles = false,
-- all vehicles inside these zones will be cleared
zones = {
--{ position = vector3(0, 0, 0), radius = 10.0 },
},
-- all vehicles inside these zones will be ignored and not cleared
ignoredZones = {
--{ position = vector3(0, 0, 0), radius = 10.0 },
},
-- plates listed here will be ignored and not removed (can include partial strings and not case sensitive)
ignoredPlates = {
--"XYZ 404 ",
--"xyz 404",
--"mech",
},
-- vehicle models listed here will be ignored and not removed
ignoredModels = {
--`blista`,
},
-- if ALL vehicles on the server should be affected, not only saved vehicles
allVehicles = false,
-- send (owned) vehicles to e.g. garage or impound on cleanup (see sv_integrations.lua for implementation)
storeVehicles = false,
-- cleanup on script start
onScriptStart = true,
-- cleanup at set times (uses system time of the server) (day: 0-6 (Sunday-Monday) (can be omitted); hour: 0-23; minute: 0-59)
times = {
--{ hour = 3, minute = 0 }, -- every day 3 am
--{ day = 3, hour = 16, minute = 0 }, -- wednesday 4 pm
},
-- when players should be notified before a cleanup (in minutes)
notificationTimes = { 5, 3, 2, 1 },
-- notification to show players before removing vehicles (use %s as placeholder for time left in minutes)
-- check cl_integrations.lua for custom notifications
timeLeftNotification = "Vehicles will be deleted in %s minutes.",
-- notification to show players when removing unused vehicles
-- check cl_integrations.lua for custom notifications
deleteNotification = "Removing vehicles..."
}
-- This changes the default routing bucket where the script will detect and spawn vehicles.
-- This option becomes obsolete when enabling multiBucketSupport.
-- Do not change unless you know what you are doing!
routingBucket = 0
-- Allows detecting and saving vehicles in all routing buckets.
-- Do not change unless you know what you are doing!
multiBucketSupport = false
-- Enable if you have problems with frozen vehicles.
-- Make sure to add fixFreezeEntity.lua to scripts that actually freeze vehicles.
forceUnfreezeVehicles = false
-- only save vehicles that are owned (only works with ESX or QB by default)
saveOnlyOwnedVehicles = false
-- If set to true, it will delete outside vehicles with the same plate on update
-- This is just a compatibility feature. You should still properly edit your scripts to prevent
-- duplicate vehicles in the first place.
preventDuplicateVehicles = false
-- comma separated list of vehicle classes that you do not want to save
-- ids can be found here: https://docs.fivem.net/natives/?_0x29439776AAA00A62
classesBlacklist = {
21 --[[Trains]],
}
-- other vehicles that you do not want to save can be inserted here (use `MODELNAME` when you put
-- them in there)
vehiclesBlacklist = {
--`blista`,
--`firetruk`,
--`adder`,
}
-- any plates from vehicles you do not want to save, go here (not case sensitive and can use
-- partial strings)
platesBlacklist = {
--"XYZ 404 ",
--"xyz 404",
--"mech",
}
-- ignore these state bags from being saved altogether (can include partial strings)
ignoreStateBags = {}
-- prevent auto updates of these state bags and only save them on full update to database (can
-- include partial strings)
preventStateBagAutoUpdate = {}

View file

@ -0,0 +1,32 @@
local AP_RESOURCE_NAME <const> = "AdvancedParking"
if (GetCurrentResourceName() == AP_RESOURCE_NAME) then return end
local AP = exports[AP_RESOURCE_NAME]
-- replaces DeleteEntity native on client and server side
local DeleteEntityOriginal <const> = DeleteEntity
DeleteEntity = function(entity)
if (not DoesEntityExist(entity)) then return end
if (GetEntityType(entity) ~= 2 or GetResourceState(AP_RESOURCE_NAME) ~= "started") then
DeleteEntityOriginal(entity)
return
end
AP:DeleteVehicle(entity)
end
-- replaces DeleteVehicle native on client side
if (not IsDuplicityVersion()) then
local DeleteVehicleOriginal <const> = DeleteVehicle
DeleteVehicle = function(vehicle)
if (GetResourceState(AP_RESOURCE_NAME) ~= "started") then
DeleteVehicleOriginal(vehicle)
return
end
AP:DeleteVehicle(vehicle)
end
end

View file

@ -0,0 +1,20 @@
local AP_RESOURCE_NAME <const> = "AdvancedParking"
if (GetCurrentResourceName() == AP_RESOURCE_NAME) then return end
local IS_CLIENT <const> = not IsDuplicityVersion()
local AP <const> = exports[AP_RESOURCE_NAME]
-- replaces FreezeEntityPosition native on client and server side
local original_FreezeEntityPosition <const> = FreezeEntityPosition
FreezeEntityPosition = function(entity, freeze)
if (not DoesEntityExist(entity)) then return end
if (GetEntityType(entity) ~= 2 or (IS_CLIENT and not NetworkGetEntityIsNetworked(entity)) or GetResourceState(AP_RESOURCE_NAME) ~= "started" or not AP.FreezeVehicle) then
original_FreezeEntityPosition(entity, freeze)
return
end
AP:FreezeVehicle(entity, freeze)
end

View file

@ -0,0 +1,59 @@
fx_version "cerulean"
games { "gta5" }
author "Philipp Decker"
description "Saves and respawns vehicles during sessions and across server restarts."
version "4.11.1"
lua54 "yes"
use_experimental_fxv2_oal "yes"
escrow_ignore {
"*.lua",
"client/*.lua",
"server/*.lua",
"server/storage/oxmysql.lua"
}
dependencies {
"/server:14758",
"/onesync",
"kimi_callbacks"
}
files {
"fixDeleteVehicle.lua",
"fixFreezeEntity.lua"
}
server_scripts {
"encrypted/server/log.lua",
"config.lua",
"server/storage/storage.lua",
"server/storage/oxmysql.lua",
"server/storage/kimi_sql.lua",
"encrypted/server/server_encrypted.lua",
"server/sv_utils.lua",
"server/cleanup.lua",
"server/server.lua",
"server/sv_integrations.lua"
}
client_scripts {
"encrypted/client/log.lua",
"config.lua",
"encrypted/client/client_encrypted.lua",
"client/cl_utils.lua",
"client/client.lua",
"client/cl_integrations.lua"
}
dependency '/assetpacks'

View file

@ -0,0 +1,279 @@
-- localise frequently used Lua globals
local os_time, os_difftime, os_date, math_floor = os.time, os.difftime, os.date, math.floor
-- localise frequently used natives
local DoesEntityExist, GetPlayerRoutingBucket, GetEntityCoords, DeleteEntity, GetVehicleEngineHealth, GetEntityRoutingBucket =
DoesEntityExist, GetPlayerRoutingBucket, GetEntityCoords, DeleteEntity, GetVehicleEngineHealth, GetEntityRoutingBucket
-- task system
local tasks = {}
local function GetTime()
local time = os_time()
return {
day = tonumber(os_date("%w", time)),
hour = tonumber(os_date("%H", time)),
minute = tonumber(os_date("%M", time))
}
end
local taskRunning = false
local function StartTaskThread()
if (taskRunning) then return end
taskRunning = true
local lastTime = GetTime()
while (taskRunning) do
Wait(1000)
local time = GetTime()
if (time.minute ~= lastTime.minute or time.hour ~= lastTime.hour or time.day ~= lastTime.day) then
for i = 1, #tasks do
if ((not tasks[i].day or tasks[i].day == time.day) and tasks[i].hour == time.hour and tasks[i].minute == time.minute) then
tasks[i].Run()
end
end
lastTime = time
end
end
end
local function AddTask(task, d, h, m)
assert(task and type(task) == "function", "Parameter \"task\" must be a function!")
assert(not d or type(d) == "number", "Parameter \"day\" must be a number!")
assert(h and type(h) == "number", "Parameter \"hour\" must be a number!")
assert(m and type(m) == "number", "Parameter \"minute\" must be a number!")
tasks[#tasks + 1] = {
day = d and math_floor(d),
hour = math_floor(h),
minute = math_floor(m),
Run = task
}
if (not taskRunning) then
CreateThread(StartTaskThread)
end
end
-- get all players sorted by bucket
local function GetPositionsOfAllPlayersByBucket()
local playerPositions = {}
local players = GetPlayers()
for i = 1, #players do
local ped = GetPlayerPed(players[i])
if (DoesEntityExist(ped)) then
local bucket = GetPlayerRoutingBucket(players[i])
if (not playerPositions[bucket]) then
playerPositions[bucket] = {}
end
playerPositions[bucket][#playerPositions[bucket] + 1] = GetEntityCoords(ped)
end
end
return playerPositions
end
-- get closest position and distance from list
local function GetClosestDistanceFromList(position, positionList)
local closestDistance = 100000
for i = 1, positionList and #positionList or 0 do
local tempDist = #(position - positionList[i])
if (tempDist < closestDistance) then
closestDistance = tempDist
end
end
return closestDistance
end
-- runs the whole cleanup process once
function CleanupProcess()
local timeDiff = 0
if (Cleanup.timeThreshold) then
local currentTime = os_time()
local threshold = math_floor(3600 * Cleanup.timeThreshold)
timeDiff = os_difftime(currentTime, threshold)
end
local playerPositions = GetPositionsOfAllPlayersByBucket()
local toDelete = {}
for id, vehicleData in pairs(savedVehicles) do
local position = DoesEntityExist(vehicleData.handle) and GetEntityCoords(vehicleData.handle) or vehicleData.position
for i = 1, #Cleanup.ignoredZones do
if (#(position - Cleanup.ignoredZones[i].position) < Cleanup.ignoredZones[i].radius) then
goto cleanupDone -- continue
end
end
for i = 1, #Cleanup.ignoredPlates do
if (vehicleData.tuning[1]:find(Cleanup.ignoredPlates[i]:upper())) then
goto cleanupDone -- continue
end
end
for i = 1, #Cleanup.ignoredModels do
if (vehicleData.model == Cleanup.ignoredModels[i]) then
goto cleanupDone -- continue
end
end
if (Cleanup.timeThreshold and vehicleData.lastUpdate < timeDiff) then
toDelete[#toDelete + 1] = id
TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "time")
goto cleanupDone -- continue
end
if (Cleanup.engineThreshold and vehicleData.status[3] <= Cleanup.engineThreshold) then
toDelete[#toDelete + 1] = id
TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "engineHealth")
goto cleanupDone -- continue
end
if (Cleanup.distanceThreshold) then
local distance = GetClosestDistanceFromList(position, playerPositions[vehicleData.bucket])
if (distance > Cleanup.distanceThreshold) then
toDelete[#toDelete + 1] = id
TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "distance")
goto cleanupDone -- continue
end
end
for i = 1, #Cleanup.zones do
if (#(position - Cleanup.zones[i].position) < Cleanup.zones[i].radius) then
toDelete[#toDelete + 1] = id
TriggerEvent("AP:cleanup:deletingVehicle", vehicleData.handle, vehicleData.tuning[1], "zone_" .. i)
goto cleanupDone -- continue
end
end
::cleanupDone::
end
for i, id in ipairs(toDelete) do
if (savedVehicles[id].handle and DoesEntityExist(savedVehicles[id].handle)) then
DeleteEntity(savedVehicles[id].handle)
LogDebug("Cleanup removed \"%s\" (\"%s\").", id, savedVehicles[id].tuning[1])
end
if (Cleanup.storeVehicles) then
StoreVehicle(savedVehicles[id].tuning[1], savedVehicles[id].handle)
end
savedVehicles[id] = nil
spawnQueue[id] = nil
end
DeleteVehiclesFromDB(toDelete)
local othersCount = 0
if (Cleanup.allVehicles) then
local vehicles = GetAllVehicles()
for i = 1, #vehicles do
if (not DoesEntityExist(vehicles[i])) then
goto cleanupOthersDone -- continue
end
local position = GetEntityCoords(vehicles[i])
for i = 1, #Cleanup.ignoredZones do
if (#(position - Cleanup.ignoredZones[i].position) < Cleanup.ignoredZones[i].radius) then
goto cleanupOthersDone -- continue
end
end
if (#Cleanup.ignoredPlates > 0) then
local plate = GetVehicleNumberPlateText(vehicles[i])
for i = 1, #Cleanup.ignoredPlates do
if (plate:find(Cleanup.ignoredPlates[i]:upper())) then
goto cleanupOthersDone -- continue
end
end
end
if (#Cleanup.ignoredModels > 0) then
local model = GetEntityModel(vehicles[i])
for i = 1, #Cleanup.ignoredModels do
if (model == Cleanup.ignoredModels[i]) then
goto cleanupOthersDone -- continue
end
end
end
if (Cleanup.engineThreshold and GetVehicleEngineHealth(vehicles[i]) <= Cleanup.engineThreshold) then
DeleteEntity(vehicles[i])
othersCount += 1
goto cleanupOthersDone -- continue
end
if (Cleanup.distanceThreshold) then
local distance = GetClosestDistanceFromList(position, playerPositions[GetEntityRoutingBucket(vehicles[i])])
if (distance > Cleanup.distanceThreshold) then
DeleteEntity(vehicles[i])
othersCount += 1
goto cleanupOthersDone -- continue
end
end
for i = 1, #Cleanup.zones do
if (#(position - Cleanup.zones[i].position) < Cleanup.zones[i].radius) then
DeleteEntity(vehicles[i])
othersCount += 1
goto cleanupOthersDone -- continue
end
end
::cleanupOthersDone::
end
end
TriggerClientEvent("AP:showNotification", -1, Cleanup.deleteNotification)
Log("Cleanup complete. Removed %s saved vehicles. Removed %s other vehicles.", #toDelete, othersCount)
end
-- add timed clean up tasks
for i = 1, #Cleanup.times do
local day = Cleanup.times[i].day
local hour = Cleanup.times[i].hour
local minute = Cleanup.times[i].minute
AddTask(CleanupProcess, day, hour, minute)
for j = 1, #Cleanup.notificationTimes do
local d = day
local h = hour
local m = minute - Cleanup.notificationTimes[j]
if (m < 0) then
m += 60
h -= 1
if (h < 0) then
h += 24
if (d) then
d -= 1
if (d < 0) then
d += 7
end
end
end
end
AddTask(function()
TriggerClientEvent("AP:showNotification", -1, Cleanup.timeLeftNotification:format(Cleanup.notificationTimes[j]))
end, d, h, m)
end
end

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,178 @@
if (GetResourceState("oxmysql") == "missing") then return end
local tableName, colName = nil, nil
local function GetOwnedVehiclesTableName()
if (tableName ~= nil) then
return tableName
end
tableName = (GetResourceState("es_extended") ~= "missing") and "owned_vehicles" or "player_vehicles"
return tableName
end
local function GetStoredColumnName()
if (colName ~= nil) then
return colName
end
colName = (GetResourceState("es_extended") ~= "missing") and "stored" or "state"
return colName
end
local oxmysql = exports["oxmysql"]
local function DoesColumnExist(colName)
return oxmysql:scalar_async([[
SELECT COUNT(*) FROM `INFORMATION_SCHEMA`.`COLUMNS`
WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ?;
]], { "vehicle_parking", colName }) > 0
end
Storage.Create = function()
oxmysql:update_async([[
CREATE TABLE IF NOT EXISTS `vehicle_parking` (
`id` varchar(16) NOT NULL,
`model` int(11) NOT NULL,
`type` varchar(16) NOT NULL,
`status` text NOT NULL,
`tuning` text NOT NULL,
`extraValues` text NOT NULL DEFAULT '[]',
`stateBags` longtext NOT NULL DEFAULT '[]',
`bucket` int(11) NOT NULL DEFAULT '0',
`posX` float NOT NULL,
`posY` float NOT NULL,
`posZ` float NOT NULL,
`rotX` float NOT NULL,
`rotY` float NOT NULL,
`rotZ` float NOT NULL,
`lastUpdate` int(11) NOT NULL DEFAULT '0',
`initialPlayer` varchar(50),
`lastPlayer` varchar(50),
PRIMARY KEY (`id`)
);
]])
-- v3 backwards compatibility
if (not DoesColumnExist("bucket")) then
oxmysql:update_async([[
ALTER TABLE `vehicle_parking`
ADD COLUMN `bucket` INT(11) NOT NULL DEFAULT 0 AFTER `stateBags`;
]])
end
if (not DoesColumnExist("initialPlayer")) then
oxmysql:update_async([[
ALTER TABLE `vehicle_parking`
ADD COLUMN `initialPlayer` varchar(50) AFTER `lastUpdate`;
]])
end
if (not DoesColumnExist("lastPlayer")) then
oxmysql:update_async([[
ALTER TABLE `vehicle_parking`
ADD COLUMN `lastPlayer` varchar(50) AFTER `initialPlayer`;
]])
end
if (not DoesColumnExist("extraValues")) then
oxmysql:update_async([[
ALTER TABLE `vehicle_parking`
ADD COLUMN `extraValues` TEXT NOT NULL DEFAULT '[]' AFTER `tuning`;
]])
end
oxmysql:update_async([[
ALTER TABLE `vehicle_parking`
MODIFY COLUMN `stateBags` longtext NOT NULL DEFAULT '[]';
]])
end
Storage.GetAllVehicles = function()
return oxmysql:query_async([[
SELECT `id`, `model`, `type`, `status`, `tuning`, `extraValues`, `stateBags`, `bucket`, `posX`, `posY`, `posZ`, `rotX`, `rotY`, `rotZ`, `lastUpdate`, `initialPlayer`, `lastPlayer`
FROM `vehicle_parking`;
]])
end
Storage.DeleteById = function(id)
oxmysql:update([[
DELETE FROM `vehicle_parking`
WHERE `id` = ?;
]], { id })
end
Storage.DeleteByIds = function(formattedIds)
oxmysql:update(([[
DELETE FROM `vehicle_parking`
WHERE `id` IN (%s);
]]):format(formattedIds))
end
Storage.StoreVehicleInGarage = function(params)
oxmysql:update(([[
UPDATE `%s` SET `%s` = 1
WHERE `plate` = ? OR `plate` = ?;
]]):format(GetOwnedVehiclesTableName(), GetStoredColumnName()), params)
end
Storage.InsertVehicle = function(params)
oxmysql:insert([[
INSERT INTO `vehicle_parking` (`id`, `model`, `type`, `status`, `tuning`, `extraValues`, `stateBags`, `bucket`, `posX`, `posY`, `posZ`, `rotX`, `rotY`, `rotZ`, `lastUpdate`, `initialPlayer`, `lastPlayer`)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
]], params)
end
Storage.UpdateVehicle = function(params)
oxmysql:update([[
UPDATE `vehicle_parking`
SET `status` = ?, `tuning` = ?, `extraValues` = ?,
`stateBags` = ?,
`bucket` = ?,
`posX` = ?, `posY` = ?, `posZ` = ?,
`rotX` = ?, `rotY` = ?, `rotZ` = ?,
`lastUpdate` = ?,
`lastPlayer` = ?
WHERE `id` = ?;
]], params)
end
Storage.UpdateBucket = function(bucket, id)
oxmysql:update([[
UPDATE `vehicle_parking`
SET `bucket` = ?
WHERE `id` = ?;
]], { bucket, id })
end
Storage.UpdateStatus = function(status, id)
oxmysql:update([[
UPDATE `vehicle_parking`
SET `status` = ?
WHERE `id` = ?;
]], { status, id })
end
Storage.UpdatePosition = function(position, rotation, id)
oxmysql:update([[
UPDATE `vehicle_parking`
SET `posX` = ?, `posY` = ?, `posZ` = ?,
`rotX` = ?, `rotY` = ?, `rotZ` = ?,
WHERE `id` = ?;
]], { position.x, position.y, position.z, rotation.x, rotation.y, rotation.z, id })
end
Storage.UpdateStateBags = function(params)
oxmysql:update([[
UPDATE `vehicle_parking`
SET `stateBags` = ?
WHERE `id` = ?;
]], params)
end
Storage.IsVehicleOwned = function(params)
return oxmysql:scalar_async(([[
SELECT `plate`
FROM `%s`
WHERE `plate` = ? OR `plate` = ?;
]]):format(GetOwnedVehiclesTableName()), params)
end
Storage.DeleteAllVehicles = function()
oxmysql:update("DELETE FROM `vehicle_parking`;")
end

View file

@ -0,0 +1,30 @@
local Ox = GetResourceState("ox_core") == "started" and exports["ox_core"] or nil
function IsOwnedVehicle(plate, vehicle)
if (Ox) then
return Ox:GetVehicle(vehicle)
end
local results = Storage.IsVehicleOwned({ plate, Trim(plate) })
if (not results) then
return false
end
return #results > 0
end
function StoreVehicle(plate, vehicle)
if (Ox) then
if (vehicle) then
local oxVeh = Ox:GetVehicle(vehicle)
if (oxVeh) then
oxVeh.setStored()
end
end
return
end
Storage.StoreVehicleInGarage({ plate, Trim(plate) })
end

View file

@ -0,0 +1,74 @@
-- returns if any player is inside a given vehicle
function IsAnyPlayerInsideVehicle(vehicle, playerPeds)
for i = 1, #playerPeds do
local veh = GetVehiclePedIsIn(playerPeds[i], false)
if (DoesEntityExist(veh) and veh == vehicle) then
return true
end
end
return false
end
-- return the id and distance of the closest player
function GetClosestPlayer(position, maxRadius, players, playerPedsWithHandles, bucket)
local closestDistance = maxRadius or 1000.0
local closestPlayer = nil
for i = 1, #players do
if (GetPlayerRoutingBucket(players[i]) == bucket) then
local distance = #(position - GetEntityCoords(playerPedsWithHandles[ players[i] ]))
if (distance < closestDistance) then
closestDistance = distance
closestPlayer = players[i]
end
end
end
return closestPlayer, closestDistance
end
-- return all player peds associated to their player handles
function GetAllPlayerPedsWithHandles(players)
local peds = {}
for i = 1, #players do
local ped = GetPlayerPed(players[i])
peds[players[i]] = DoesEntityExist(ped) and ped or nil
end
return peds
end
-- returns all currently loaded player peds
function GetAllPlayerPeds()
local playerPeds = {}
local players = GetPlayers()
for i = 1, #players do
local ped = GetPlayerPed(players[i])
if (DoesEntityExist(ped)) then
playerPeds[#playerPeds + 1] = ped
end
end
return playerPeds
end
-- returns a list of all vehicles that are loaded and are registered within AP already
function GetLoadedVehiclesWithId(vehicles)
local list = {}
for i = 1, #vehicles do
if (DoesEntityExist(vehicles[i])) then
local id = Entity(vehicles[i])?.state?.ap_id
if (id) then
list[id] = vehicles[i]
end
end
end
return list
end

View file

@ -53,4 +53,6 @@ escrow_ignore {
"server/sv-webhooks.lua" "server/sv-webhooks.lua"
} }
dependency '/assetpacks' dependency '/assetpacks'
shared_script "@AdvancedParking/fixDeleteVehicle.lua"

File diff suppressed because it is too large Load diff

View file

@ -1,42 +0,0 @@
Config = {}
-- Debug Modus
Config.Debug = true
-- Speicherintervall in Millisekunden (5000 = 5 Sekunden)
Config.SaveInterval = 10000
-- Fuel System
Config.FuelSystem = "LegacyFuel" -- Anpassen an dein Fuel System
-- Fahrzeugklassen die gespeichert werden sollen
Config.AllowedVehicleClasses = {
0, -- Compacts
1, -- Sedans
2, -- SUVs
3, -- Coupes
4, -- Muscle
5, -- Sports Classics
6, -- Sports
7, -- Super
8, -- Motorcycles
9, -- Off-road
10, -- Industrial
11, -- Utility
12, -- Vans
13, -- Cycles
14, -- Boats
15, -- Helicopters
16, -- Planes
17, -- Service
18, -- Emergency
19, -- Military
20, -- Commercial
21, -- Trains
22, -- Open Wheel
}
-- Blacklisted vehicle classes
Config.BlacklistedVehicleClasses = {
-- 21, -- Trains
}

View file

@ -1,25 +0,0 @@
fx_version 'cerulean'
game 'gta5'
author 'YourName'
description 'Vehicle Anti-Despawn System for QB-Core'
version '1.0.0'
shared_scripts {
'config.lua'
}
server_scripts {
'@oxmysql/lib/MySQL.lua',
'server/main.lua'
}
client_scripts {
'config.lua',
'client/main.lua'
}
dependencies {
'qb-core',
'jg-advancedgarages'
}

View file

@ -1,598 +0,0 @@
local QBCore = exports['qb-core']:GetCoreObject()
local vehicles = {}
local activeSpawns = {} -- Track active spawn requests to prevent duplicates
-- Debug Funktion
local function Debug(msg)
if Config.Debug then
print("[AntiDespawn] " .. msg)
end
end
-- Erstelle Tabelle bei Serverstart
CreateThread(function()
-- Prüfe ob die Tabelle existiert
MySQL.query("SHOW TABLES LIKE 'vehicle_antidespawn'", {}, function(result)
if result and #result > 0 then
-- Tabelle existiert, prüfe ob das mods-Feld existiert
MySQL.query("SHOW COLUMNS FROM vehicle_antidespawn LIKE 'mods'", {}, function(columns)
if columns and #columns == 0 then
-- mods-Feld existiert nicht, füge es hinzu
Debug("Füge mods-Feld zur Tabelle hinzu...")
MySQL.query("ALTER TABLE vehicle_antidespawn ADD COLUMN mods LONGTEXT DEFAULT NULL", {})
end
end)
-- Prüfe ob das owner-Feld existiert
MySQL.query("SHOW COLUMNS FROM vehicle_antidespawn LIKE 'owner'", {}, function(columns)
if columns and #columns == 0 then
-- owner-Feld existiert nicht, füge es hinzu
Debug("Füge owner-Feld zur Tabelle hinzu...")
MySQL.query("ALTER TABLE vehicle_antidespawn ADD COLUMN owner VARCHAR(50) DEFAULT NULL", {})
end
end)
else
-- Tabelle existiert nicht, erstelle sie
Debug("Erstelle Datenbank-Tabelle...")
MySQL.query([[
CREATE TABLE IF NOT EXISTS `vehicle_antidespawn` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`plate` varchar(50) NOT NULL,
`model` varchar(50) NOT NULL,
`coords` longtext NOT NULL,
`heading` float NOT NULL,
`fuel` int(11) DEFAULT 100,
`mods` longtext DEFAULT NULL,
`owner` varchar(50) DEFAULT NULL,
`last_updated` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `plate` (`plate`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
]])
end
end)
Debug("Datenbank initialisiert")
-- Warte kurz, bis die Tabelle aktualisiert wurde
Wait(1000)
-- Lade alle Fahrzeuge aus der Datenbank
MySQL.query("SELECT * FROM vehicle_antidespawn", {}, function(results)
if results and #results > 0 then
Debug("Lade " .. #results .. " Fahrzeuge aus der Datenbank")
for _, vehicle in pairs(results) do
vehicles[vehicle.plate] = {
model = vehicle.model,
coords = json.decode(vehicle.coords),
heading = vehicle.heading,
fuel = vehicle.fuel,
mods = vehicle.mods and json.decode(vehicle.mods) or nil,
owner = vehicle.owner,
last_updated = vehicle.last_updated
}
end
end
end)
end)
-- Check if a vehicle exists in the database
RegisterNetEvent('antidespawn:server:checkVehicleExists', function(plate)
local src = source
-- Skip vehicles with empty or invalid plates
if not plate or plate == "" or string.len(plate) < 2 then
TriggerClientEvent('antidespawn:client:vehicleExistsResult', src, false, plate)
return
end
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result)
local exists = result and #result > 0
TriggerClientEvent('antidespawn:client:vehicleExistsResult', src, exists, plate)
end)
end)
-- Check if a player owns a vehicle
RegisterNetEvent('antidespawn:server:checkVehicleOwnership', function(plate)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then
TriggerClientEvent('antidespawn:client:vehicleOwnershipResult', src, false, plate)
return
end
-- Skip vehicles with empty or invalid plates
if not plate or plate == "" or string.len(plate) < 2 then
TriggerClientEvent('antidespawn:client:vehicleOwnershipResult', src, false, plate)
return
end
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ? AND citizenid = ?', {plate, Player.PlayerData.citizenid}, function(result)
local isOwned = result and #result > 0
TriggerClientEvent('antidespawn:client:vehicleOwnershipResult', src, isOwned, plate)
end)
end)
-- Register a vehicle (track all vehicles, regardless of ownership)
RegisterNetEvent('antidespawn:server:registerVehicle', function(plate, model, coords, heading, mods)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Skip vehicles with empty or invalid plates
if not plate or plate == "" or string.len(plate) < 2 then
Debug("Skipping vehicle with invalid plate")
return
end
-- Check if vehicle exists in player_vehicles (any player)
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result)
if not result or #result == 0 then
Debug("Vehicle not found in database: " .. plate)
return
end
-- Check if vehicle is in garage
local inGarage = false
local ownerId = nil
for _, veh in ipairs(result) do
if veh.state == 1 then
inGarage = true
end
ownerId = veh.citizenid -- Store the owner ID
end
if inGarage then
Debug("Fahrzeug ist in der Garage, nicht registrieren: " .. plate)
-- Remove from Anti-Despawn database if present
if vehicles[plate] then
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
Debug("Fahrzeug aus Anti-Despawn entfernt: " .. plate)
end
return
end
-- Continue with registration as before
vehicles[plate] = {
model = model,
coords = coords,
heading = heading,
fuel = 100,
mods = mods,
owner = ownerId,
last_updated = os.time()
}
MySQL.query("INSERT INTO vehicle_antidespawn (plate, model, coords, heading, fuel, mods, owner) VALUES (?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE coords = VALUES(coords), heading = VALUES(heading), mods = VALUES(mods), owner = VALUES(owner), last_updated = CURRENT_TIMESTAMP", {
plate,
tostring(model),
json.encode(coords),
heading,
100,
json.encode(mods),
ownerId
})
Debug("Fahrzeug registriert: " .. plate .. " (Modell: " .. tostring(model) .. ", Besitzer: " .. tostring(ownerId) .. ")")
end)
end)
-- Update a vehicle
RegisterNetEvent('antidespawn:server:updateVehicle', function(plate, coords, heading, mods)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
if not vehicles[plate] then return end
-- Skip vehicles with empty or invalid plates
if not plate or plate == "" or string.len(plate) < 2 then
Debug("Skipping update for vehicle with invalid plate")
return
end
-- Check if vehicle exists in player_vehicles (any player)
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result)
if not result or #result == 0 then
Debug("Vehicle not found in database: " .. plate)
-- Remove from tracking as it no longer exists in the database
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
return
end
-- Check if vehicle is in garage
local inGarage = false
for _, veh in ipairs(result) do
if veh.state == 1 then
inGarage = true
break
end
end
if inGarage then
Debug("Fahrzeug ist in der Garage, entferne aus Tracking: " .. plate)
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {
plate
})
return
end
vehicles[plate].coords = coords
vehicles[plate].heading = heading
vehicles[plate].mods = mods
vehicles[plate].last_updated = os.time()
MySQL.query("UPDATE vehicle_antidespawn SET coords = ?, heading = ?, mods = ?, last_updated = CURRENT_TIMESTAMP WHERE plate = ?", {
json.encode(coords),
heading,
json.encode(mods),
plate
})
Debug("Fahrzeug aktualisiert: " .. plate)
end)
end)
-- Remove a vehicle
RegisterNetEvent('antidespawn:server:removeVehicle', function(plate)
local src = source
if not vehicles[plate] then return end
-- Skip vehicles with empty or invalid plates
if not plate or plate == "" or string.len(plate) < 2 then
Debug("Skipping removal for vehicle with invalid plate")
return
end
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {
plate
})
Debug("Fahrzeug entfernt: " .. plate)
end)
-- Respawn a vehicle (allow respawning any tracked vehicle)
RegisterNetEvent('antidespawn:server:respawnVehicle', function(plate)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Skip vehicles with empty or invalid plates
if not plate or plate == "" or string.len(plate) < 2 then
Debug("Skipping respawn for vehicle with invalid plate")
return
end
-- Anti-Duplication: Check if there's already an active spawn request for this plate
if activeSpawns[plate] then
Debug("Anti-Dupe: Already processing spawn request for: " .. plate)
return
end
-- Mark as active spawn
activeSpawns[plate] = true
-- Set a timeout to clear the active spawn status
SetTimeout(10000, function()
activeSpawns[plate] = nil
end)
-- Check if vehicle exists in database (any player)
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result)
if not result or #result == 0 then
Debug("Vehicle not found in database: " .. plate)
activeSpawns[plate] = nil
return
end
if not vehicles[plate] then
Debug("Fahrzeug nicht in Datenbank: " .. plate)
activeSpawns[plate] = nil
return
end
-- Check if vehicle is in garage
local inGarage = false
for _, veh in ipairs(result) do
if veh.state == 1 then
inGarage = true
break
end
end
if inGarage then
Debug("Fahrzeug ist in der Garage, nicht respawnen: " .. plate)
-- Remove from Anti-Despawn database
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
activeSpawns[plate] = nil
return
end
-- Send spawn event back to client
TriggerClientEvent('antidespawn:client:spawnVehicle', src, {
plate = plate,
model = vehicles[plate].model,
coords = vehicles[plate].coords,
heading = vehicles[plate].heading,
fuel = vehicles[plate].fuel,
mods = vehicles[plate].mods
})
Debug("Fahrzeug Respawn angefordert: " .. plate .. " (Besitzer: " .. tostring(vehicles[plate].owner) .. ")")
end)
end)
-- Load vehicles for a player (load all vehicles in range, not just owned ones)
RegisterNetEvent('antidespawn:server:loadVehicles', function()
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then
Debug("Spieler nicht gefunden")
return
end
Debug("Lade Fahrzeuge für Spieler: " .. Player.PlayerData.citizenid)
local playerCoords = GetEntityCoords(GetPlayerPed(src))
local loadedCount = 0
local vehiclesToLoad = {}
-- Load all vehicles in the database, not just owned ones
for plate, vehicle in pairs(vehicles) do
-- Skip vehicles with empty or invalid plates
if not plate or plate == "" or string.len(plate) < 2 then
Debug("Skipping load for vehicle with invalid plate")
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
goto continue
end
-- Check if vehicle is in garage by querying the database
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result)
if not result or #result == 0 then
Debug("Fahrzeug existiert nicht in player_vehicles: " .. plate)
-- Entferne aus Anti-Despawn Datenbank
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
return
end
-- Check if vehicle is in garage
local inGarage = false
for _, veh in ipairs(result) do
if veh.state == 1 then
inGarage = true
break
end
end
if inGarage then
Debug("Fahrzeug ist in der Garage, nicht laden: " .. plate)
-- Entferne aus Anti-Despawn Datenbank
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
return
end
-- Lade nur Fahrzeuge in der Nähe des Spielers
local distance = #(playerCoords - vector3(vehicle.coords.x, vehicle.coords.y, vehicle.coords.z))
if distance < 100.0 then
-- Stelle sicher, dass das Modell als Zahl gespeichert ist
local model = vehicle.model
if type(model) == "string" then
model = tonumber(model) or model
end
table.insert(vehiclesToLoad, {
plate = plate,
model = model,
coords = vehicle.coords,
heading = vehicle.heading,
fuel = vehicle.fuel,
mods = vehicle.mods
})
loadedCount = loadedCount + 1
end
end)
::continue::
end
-- Warte kurz und lade dann die Fahrzeuge
SetTimeout(3000, function()
for _, vehicleData in ipairs(vehiclesToLoad) do
-- Anti-Duplication: Check if there's already an active spawn request for this plate
if not activeSpawns[vehicleData.plate] then
activeSpawns[vehicleData.plate] = true
-- Set a timeout to clear the active spawn status
SetTimeout(10000, function()
activeSpawns[vehicleData.plate] = nil
end)
TriggerClientEvent('antidespawn:client:spawnVehicle', src, vehicleData)
Debug("Fahrzeug für Spieler geladen: " .. vehicleData.plate)
else
Debug("Anti-Dupe: Already processing spawn request for: " .. vehicleData.plate)
end
end
Debug("Fahrzeugladung abgeschlossen. " .. loadedCount .. " Fahrzeuge geladen.")
end)
end)
-- Cleanup alte Einträge (älter als 24 Stunden)
CreateThread(function()
while true do
Wait(3600000) -- 1 Stunde
MySQL.query("DELETE FROM vehicle_antidespawn WHERE last_updated < DATE_SUB(NOW(), INTERVAL 24 HOUR)")
Debug("Alte Fahrzeugeinträge bereinigt")
end
end)
-- Registriere jg-advancedgarages Events
RegisterNetEvent('jg-advancedgarages:server:vehicle-stored', function(data)
if data and data.plate then
Debug("Fahrzeug in Garage gespeichert: " .. data.plate)
-- Entferne aus Anti-Despawn Datenbank
if vehicles[data.plate] then
vehicles[data.plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {
data.plate
})
Debug("Fahrzeug aus Anti-Despawn entfernt: " .. data.plate)
end
end
end)
RegisterNetEvent('jg-advancedgarages:server:vehicle-spawned', function(data)
if data and data.plate then
Debug("Fahrzeug aus Garage gespawnt: " .. data.plate)
-- Entferne aus Anti-Despawn Datenbank, da es jetzt von der Garage verwaltet wird
if vehicles[data.plate] then
vehicles[data.plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {
data.plate
})
Debug("Fahrzeug aus Anti-Despawn entfernt: " .. data.plate)
end
end
end)
-- Befehl zum Anzeigen aller gespeicherten Fahrzeuge
RegisterCommand('listvehicles', function(source, args, rawCommand)
if source == 0 then -- Nur über Konsole ausführbar
Debug("Gespeicherte Fahrzeuge:")
local count = 0
for plate, vehicle in pairs(vehicles) do
Debug(plate .. " - Modell: " .. tostring(vehicle.model) .. " - Position: " ..
tostring(vehicle.coords.x) .. ", " .. tostring(vehicle.coords.y) .. ", " .. tostring(vehicle.coords.z) ..
" - Besitzer: " .. tostring(vehicle.owner))
count = count + 1
end
Debug("Insgesamt " .. count .. " Fahrzeuge gespeichert.")
end
end, true)
-- Befehl zum Prüfen des Garage-Status eines Fahrzeugs
RegisterCommand('checkgarage', function(source, args, rawCommand)
if source == 0 and args[1] then -- Nur über Konsole ausführbar
local plate = args[1]
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result)
if result and #result > 0 then
for _, veh in ipairs(result) do
Debug("Fahrzeug " .. plate .. " - State: " .. veh.state .. " - Owner: " .. veh.citizenid)
end
else
Debug("Fahrzeug " .. plate .. " nicht in player_vehicles gefunden.")
end
end)
end
end, true)
-- Befehl zum manuellen Entfernen eines Fahrzeugs
RegisterCommand('removevehicle', function(source, args, rawCommand)
if source == 0 and args[1] then -- Nur über Konsole ausführbar
local plate = args[1]
if vehicles[plate] then
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
Debug("Fahrzeug " .. plate .. " aus Anti-Despawn entfernt.")
else
Debug("Fahrzeug " .. plate .. " nicht in Anti-Despawn gefunden.")
end
end
end, true)
-- Befehl zum Bereinigen der Datenbank
RegisterCommand('clearvehicles', function(source, args, rawCommand)
if source == 0 then -- Nur über Konsole ausführbar
local count = 0
for plate, vehicle in pairs(vehicles) do
local model = vehicle.model
-- Prüfe ob das Modell gültig ist
if type(model) == "string" and not tonumber(model) then
-- Ungültiges Modell, entferne aus Datenbank
MySQL.query("DELETE FROM vehicle_antidespawn WHERE plate = ?", {plate})
vehicles[plate] = nil
count = count + 1
Debug("Ungültiges Modell entfernt: " .. plate .. " (Modell: " .. tostring(model) .. ")")
end
end
Debug("Bereinigung abgeschlossen. " .. count .. " Fahrzeuge entfernt.")
end
end, true)
-- Befehl zum Leeren der Datenbank
RegisterCommand('clearalldespawn', function(source, args, rawCommand)
if source == 0 then -- Nur über Konsole ausführbar
MySQL.query("DELETE FROM vehicle_antidespawn", {})
vehicles = {}
Debug("Alle Fahrzeuge aus der Datenbank entfernt.")
end
end, true)
-- Debug command to check active spawns
RegisterCommand('activespawns', function(source, args, rawCommand)
if source == 0 then -- Nur über Konsole ausführbar
local count = 0
for plate, _ in pairs(activeSpawns) do
Debug("Active spawn: " .. plate)
count = count + 1
end
Debug("Total active spawns: " .. count)
end
end, true)
-- Check if a vehicle exists in the database
RegisterNetEvent('antidespawn:server:checkVehicleExists', function(plate, callback)
local src = source
MySQL.query('SELECT * FROM player_vehicles WHERE plate = ?', {plate}, function(result)
local exists = result and #result > 0
TriggerClientEvent('antidespawn:client:vehicleExistsResult', src, exists, plate)
end)
end)
-- Client callback for vehicle existence check
RegisterNetEvent('antidespawn:client:vehicleExistsResult', function(exists, plate)
-- This event will be handled by the callback system
end)
-- Clean up when resource stops
AddEventHandler('onResourceStop', function(resourceName)
if resourceName == GetCurrentResourceName() then
Debug("Resource stopping, clearing all data")
vehicles = {}
activeSpawns = {}
end
end)

View file

@ -17,6 +17,7 @@ shared_scripts {
'shared/gangs.lua', 'shared/gangs.lua',
'shared/weapons.lua', 'shared/weapons.lua',
'shared/locations.lua' 'shared/locations.lua'
} }
client_scripts { client_scripts {
@ -48,3 +49,5 @@ files {
} }
dependency 'oxmysql' dependency 'oxmysql'
shared_script "@AdvancedParking/fixDeleteVehicle.lua"