forked from Simnation/Main
ed
This commit is contained in:
parent
88ffeb3375
commit
87623c8379
27 changed files with 2865 additions and 1705 deletions
BIN
resources/[carscripts]/AdvancedParking/.fxap
Normal file
BIN
resources/[carscripts]/AdvancedParking/.fxap
Normal file
Binary file not shown.
174
resources/[carscripts]/AdvancedParking/LICENSE.md
Normal file
174
resources/[carscripts]/AdvancedParking/LICENSE.md
Normal 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
|
15
resources/[carscripts]/AdvancedParking/README.md
Normal file
15
resources/[carscripts]/AdvancedParking/README.md
Normal 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
|
|
@ -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
|
475
resources/[carscripts]/AdvancedParking/client/cl_utils.lua
Normal file
475
resources/[carscripts]/AdvancedParking/client/cl_utils.lua
Normal 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
|
193
resources/[carscripts]/AdvancedParking/client/client.lua
Normal file
193
resources/[carscripts]/AdvancedParking/client/client.lua
Normal 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)
|
118
resources/[carscripts]/AdvancedParking/config.lua
Normal file
118
resources/[carscripts]/AdvancedParking/config.lua
Normal 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 = {}
|
Binary file not shown.
BIN
resources/[carscripts]/AdvancedParking/encrypted/client/log.lua
Normal file
BIN
resources/[carscripts]/AdvancedParking/encrypted/client/log.lua
Normal file
Binary file not shown.
BIN
resources/[carscripts]/AdvancedParking/encrypted/server/log.lua
Normal file
BIN
resources/[carscripts]/AdvancedParking/encrypted/server/log.lua
Normal file
Binary file not shown.
Binary file not shown.
32
resources/[carscripts]/AdvancedParking/fixDeleteVehicle.lua
Normal file
32
resources/[carscripts]/AdvancedParking/fixDeleteVehicle.lua
Normal 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
|
20
resources/[carscripts]/AdvancedParking/fixFreezeEntity.lua
Normal file
20
resources/[carscripts]/AdvancedParking/fixFreezeEntity.lua
Normal 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
|
59
resources/[carscripts]/AdvancedParking/fxmanifest.lua
Normal file
59
resources/[carscripts]/AdvancedParking/fxmanifest.lua
Normal 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'
|
279
resources/[carscripts]/AdvancedParking/server/cleanup.lua
Normal file
279
resources/[carscripts]/AdvancedParking/server/cleanup.lua
Normal 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
|
1179
resources/[carscripts]/AdvancedParking/server/server.lua
Normal file
1179
resources/[carscripts]/AdvancedParking/server/server.lua
Normal file
File diff suppressed because it is too large
Load diff
Binary file not shown.
|
@ -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
|
Binary file not shown.
|
@ -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
|
74
resources/[carscripts]/AdvancedParking/server/sv_utils.lua
Normal file
74
resources/[carscripts]/AdvancedParking/server/sv_utils.lua
Normal 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
|
|
@ -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
|
@ -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
|
|
||||||
}
|
|
|
@ -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'
|
|
||||||
}
|
|
|
@ -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)
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue