1
0
Fork 0
forked from Simnation/Main
This commit is contained in:
Nordi98 2025-08-06 15:36:50 +02:00
parent 6d22d5f77c
commit 63fbc60a00
86 changed files with 8352 additions and 3428 deletions

View file

@ -0,0 +1,488 @@
-- logs text to the console
function Log(text)
if (Config.isDebug) then
Log(text)
end
end
-- Return all vehicle modifications in an array
function GetVehicleModifications(vehicle)
local color1, color2 = GetVehicleColours(vehicle)
local pearlescentColor, wheelColor = GetVehicleExtraColours(vehicle)
local extras = {}
for i = 0, 20, 1 do
if (DoesExtraExist(vehicle, i)) then
if (IsVehicleExtraTurnedOn(vehicle, i)) then
table.insert(extras, { i, 0 })
else
table.insert(extras, { i, 1 })
end
end
end
local tiresBurst = {}
for i = 0, 5, 1 do
if (IsVehicleTyreBurst(vehicle, i, true)) then
table.insert(tiresBurst, { i, true })
elseif (IsVehicleTyreBurst(vehicle, i, false)) then
table.insert(tiresBurst, { i, false })
end
end
local doorsMissing = {}
for i = 0, 7, 1 do
if (IsVehicleDoorDamaged(vehicle, i)) then
table.insert(doorsMissing, i)
end
end
local windowsBroken = {}
--for i = 0, 13, 1 do
-- if (not IsVehicleWindowIntact(vehicle, i)) then
-- table.insert(windowsBroken, i)
-- end
--end
-- custom colors
local hasCustomPrimaryColor = GetIsVehiclePrimaryColourCustom(vehicle)
local customPrimaryColor = nil
if (hasCustomPrimaryColor) then
local r, g, b = GetVehicleCustomPrimaryColour(vehicle)
customPrimaryColor = { r, g, b }
end
local hasCustomSecondaryColor = GetIsVehicleSecondaryColourCustom(vehicle)
local customSecondaryColor = nil
if (hasCustomSecondaryColor) then
local r, g, b = GetVehicleCustomSecondaryColour(vehicle)
customSecondaryColor = { r, g, b }
end
return {
-- 1 model
GetEntityModel(vehicle),
-- 2 lockStatus
GetVehicleDoorLockStatus(vehicle),
-- 3 health
math.floor(GetEntityHealth(vehicle) * 10.0) / 10.0,
-- 4 bodyHealth
math.floor(GetVehicleBodyHealth(vehicle) * 10.0) / 10.0,
-- 5 engineHealth
math.floor(GetVehicleEngineHealth(vehicle) * 10.0) / 10.0,
-- 6 petrolTankHealth
math.floor(GetVehiclePetrolTankHealth(vehicle) * 10.0) / 10.0,
-- 7 dirtLevel
math.floor(GetVehicleDirtLevel(vehicle) * 10.0) / 10.0,
-- 8 fuelLevel
math.floor(exports[Config.exports]:GetFuel(vehicle) * 10.0) / 10.0,
---------------------------------------------------------------------- Implementierung Miho
--math.floor(DecorGetFloat(vehicle, Config.FuelDecor)*10) / 10.0,
---------------------------------------------------------------------- Implementierung Miho ENDE
-- 9 plateIndex
GetVehicleNumberPlateTextIndex(vehicle),
-- 10 primaryColor
color1,
-- 11 secondaryColor
color2,
-- 12 pearlescentColor
pearlescentColor,
-- 13 wheelColor
wheelColor,
-- 14 wheelType
GetVehicleWheelType(vehicle),
-- 15 customWheelsFront
GetVehicleModVariation(vehicle, 23);
-- 16 customWheelsBack
GetVehicleModVariation(vehicle, 24);
-- 17 windowTint
GetVehicleWindowTint(vehicle),
-- 18 enabledNeon
{
IsVehicleNeonLightEnabled(vehicle, 0),
IsVehicleNeonLightEnabled(vehicle, 1),
IsVehicleNeonLightEnabled(vehicle, 2),
IsVehicleNeonLightEnabled(vehicle, 3),
},
-- 19 neonColor
table.pack(GetVehicleNeonLightsColour(vehicle)),
-- 20 tireSmokeColor
table.pack(GetVehicleTyreSmokeColor(vehicle)),
-- 21 extras
extras,
-- 22-32 mods
GetVehicleMod(vehicle, 0),
GetVehicleMod(vehicle, 1),
GetVehicleMod(vehicle, 2),
GetVehicleMod(vehicle, 3),
GetVehicleMod(vehicle, 4),
GetVehicleMod(vehicle, 5),
GetVehicleMod(vehicle, 6),
GetVehicleMod(vehicle, 7),
GetVehicleMod(vehicle, 8),
GetVehicleMod(vehicle, 9),
GetVehicleMod(vehicle, 10),
-- 33-38 mods
GetVehicleMod(vehicle, 11),
GetVehicleMod(vehicle, 12),
GetVehicleMod(vehicle, 13),
GetVehicleMod(vehicle, 14),
GetVehicleMod(vehicle, 15),
GetVehicleMod(vehicle, 16),
-- 39-41 mods
IsToggleModOn(vehicle, 18),
IsToggleModOn(vehicle, 20),
IsToggleModOn(vehicle, 22),
-- 42-43 mods
GetVehicleMod(vehicle, 23),
GetVehicleMod(vehicle, 24),
-- 44-66 mods
GetVehicleMod(vehicle, 25),
GetVehicleMod(vehicle, 26),
GetVehicleMod(vehicle, 27),
GetVehicleMod(vehicle, 28),
GetVehicleMod(vehicle, 29),
GetVehicleMod(vehicle, 30),
GetVehicleMod(vehicle, 31),
GetVehicleMod(vehicle, 32),
GetVehicleMod(vehicle, 33),
GetVehicleMod(vehicle, 34),
GetVehicleMod(vehicle, 35),
GetVehicleMod(vehicle, 36),
GetVehicleMod(vehicle, 37),
GetVehicleMod(vehicle, 38),
GetVehicleMod(vehicle, 39),
GetVehicleMod(vehicle, 40),
GetVehicleMod(vehicle, 41),
GetVehicleMod(vehicle, 42),
GetVehicleMod(vehicle, 43),
GetVehicleMod(vehicle, 44),
GetVehicleMod(vehicle, 45),
GetVehicleMod(vehicle, 46),
GetVehicleMod(vehicle, 48),
-- 67 livery
GetVehicleLivery(vehicle),
-- 68 missingDoors
doorsMissing,
-- 69 bulletproofTires
not GetVehicleTyresCanBurst(vehicle),
-- 70 tiresBurst
tiresBurst,
-- 71 brokenWindows
windowsBroken,
-- 72 xenon lights
GetVehicleXenonLightsColour(vehicle),
-- 73 custom primary color
customPrimaryColor,
-- 74 custom secondary color
customSecondaryColor,
-- 75 interior color
GetVehicleInteriorColor(vehicle)
}
end
-- Apply all modifications to a vehicle entity
function SetVehicleModifications(vehicle, plate, modifications)
SetVehicleModKit(vehicle, 0)
-- plate
SetVehicleNumberPlateText(vehicle, plate)
SetVehicleNumberPlateTextIndex(vehicle, modifications[9])
-- lockStatus
SetVehicleDoorsLocked(vehicle, modifications[2])
-- colours
SetVehicleColours(vehicle, modifications[10], modifications[11])
if (modifications[73]) then
SetVehicleCustomPrimaryColour(vehicle, modifications[73][1], modifications[73][2], modifications[73][3])
end
if (modifications[74]) then
SetVehicleCustomSecondaryColour(vehicle, modifications[74][1], modifications[74][2], modifications[74][3])
end
if (modifications[75]) then
SetVehicleInteriorColor(vehicle, modifications[75])
end
SetVehicleExtraColours(vehicle, modifications[12], modifications[13])
SetVehicleTyreSmokeColor(vehicle, modifications[20][1], modifications[20][2], modifications[20][3])
-- wheels
SetVehicleWheelType(vehicle, modifications[14])
-- windows
SetVehicleWindowTint(vehicle, modifications[17])
-- neonlight
SetVehicleNeonLightEnabled(vehicle, 0, modifications[18][1])
SetVehicleNeonLightEnabled(vehicle, 1, modifications[18][2])
SetVehicleNeonLightEnabled(vehicle, 2, modifications[18][3])
SetVehicleNeonLightEnabled(vehicle, 3, modifications[18][4])
SetVehicleNeonLightsColour(vehicle, modifications[19][1], modifications[19][2], modifications[19][3])
-- mods
SetVehicleMod(vehicle, 0, modifications[22], modifications[15])
SetVehicleMod(vehicle, 1, modifications[23], modifications[15])
SetVehicleMod(vehicle, 2, modifications[24], modifications[15])
SetVehicleMod(vehicle, 3, modifications[25], modifications[15])
SetVehicleMod(vehicle, 4, modifications[26], modifications[15])
SetVehicleMod(vehicle, 5, modifications[27], modifications[15])
SetVehicleMod(vehicle, 6, modifications[28], modifications[15])
SetVehicleMod(vehicle, 7, modifications[29], modifications[15])
SetVehicleMod(vehicle, 8, modifications[30], modifications[15])
SetVehicleMod(vehicle, 9, modifications[31], modifications[15])
SetVehicleMod(vehicle, 10, modifications[32], modifications[15])
SetVehicleMod(vehicle, 11, modifications[33], modifications[15])
SetVehicleMod(vehicle, 12, modifications[34], modifications[15])
SetVehicleMod(vehicle, 13, modifications[35], modifications[15])
SetVehicleMod(vehicle, 14, modifications[36], modifications[15])
SetVehicleMod(vehicle, 15, modifications[37], modifications[15])
SetVehicleMod(vehicle, 16, modifications[38], modifications[15])
ToggleVehicleMod(vehicle, 18, modifications[39])
ToggleVehicleMod(vehicle, 20, modifications[40])
ToggleVehicleMod(vehicle, 22, modifications[41])
SetVehicleMod(vehicle, 23, modifications[42], modifications[15])
SetVehicleMod(vehicle, 24, modifications[43], modifications[16])
SetVehicleMod(vehicle, 25, modifications[44], modifications[15])
SetVehicleMod(vehicle, 26, modifications[45], modifications[15])
SetVehicleMod(vehicle, 27, modifications[46], modifications[15])
SetVehicleMod(vehicle, 28, modifications[47], modifications[15])
SetVehicleMod(vehicle, 29, modifications[48], modifications[15])
SetVehicleMod(vehicle, 30, modifications[49], modifications[15])
SetVehicleMod(vehicle, 31, modifications[50], modifications[15])
SetVehicleMod(vehicle, 32, modifications[51], modifications[15])
SetVehicleMod(vehicle, 33, modifications[52], modifications[15])
SetVehicleMod(vehicle, 34, modifications[53], modifications[15])
SetVehicleMod(vehicle, 35, modifications[54], modifications[15])
SetVehicleMod(vehicle, 36, modifications[55], modifications[15])
SetVehicleMod(vehicle, 37, modifications[56], modifications[15])
SetVehicleMod(vehicle, 38, modifications[57], modifications[15])
SetVehicleMod(vehicle, 39, modifications[58], modifications[15])
SetVehicleMod(vehicle, 40, modifications[59], modifications[15])
SetVehicleMod(vehicle, 41, modifications[60], modifications[15])
SetVehicleMod(vehicle, 42, modifications[61], modifications[15])
SetVehicleMod(vehicle, 43, modifications[62], modifications[15])
SetVehicleMod(vehicle, 44, modifications[63], modifications[15])
SetVehicleMod(vehicle, 45, modifications[64], modifications[15])
SetVehicleMod(vehicle, 46, modifications[65], modifications[15])
SetVehicleMod(vehicle, 48, modifications[66], modifications[15])
SetVehicleLivery(vehicle, modifications[67])
-- extras
for i = 1, #modifications[21], 1 do
SetVehicleExtra(vehicle, modifications[21][i][1], modifications[21][i][2])
end
-- stats
SetEntityHealth(vehicle, modifications[3])
SetVehicleBodyHealth(vehicle, modifications[4])
SetVehicleEngineHealth(vehicle, modifications[5])
SetVehiclePetrolTankHealth(vehicle, modifications[6])
if (Config.renderScorched and (modifications[5] < -3999.0 or modifications[6] < -999.0)) then
TriggerServerEvent("mh_Parking:renderScorched", NetworkGetNetworkIdFromEntity(vehicle), true)
end
SetVehicleDirtLevel(vehicle, modifications[7])
exports[Config.exports]:SetFuel(vehicle, modifications[8])
---------------------------------------------------------------------- Implementierung Miho
--[[
fuel = modifications[8]
if type(fuel) == 'number' and fuel >= 0 and fuel <= 100 then
SetVehicleFuelLevel(vehicle, fuel + 0.0)
DecorSetFloat(vehicle, Config.FuelDecor, GetVehicleFuelLevel(vehicle))
end
]]
---------------------------------------------------------------------- Implementierung Miho ENDE
-- doors
for i = 1, #modifications[68], 1 do
SetVehicleDoorBroken(vehicle, modifications[68][i], true)
end
-- tires
SetVehicleTyresCanBurst(vehicle, not modifications[69])
if (not modifications[69]) then
for i = 1, #modifications[70], 1 do
SetVehicleTyreBurst(vehicle, modifications[70][i][1], modifications[70][i][2], 1000.0)
end
end
-- windows
for i = 1, #modifications[71], 1 do
SmashVehicleWindow(vehicle, modifications[71][i])
end
-- xenon lights
if (modifications[72]) then
SetVehicleXenonLightsColour(vehicle, modifications[72])
end
end
-- Return closest loaded vehicle entity or nil if no vehicle is found
function GetClosestVehicle(position, maxRadius)
local vehicles = GetAllVehicles()
local dist = maxRadius
local closestVehicle = nil
for i=1, #vehicles, 1 do
local vehicleCoords = GetEntityCoords(vehicles[i])
local tempDist = Vdist(vehicleCoords.x, vehicleCoords.y, vehicleCoords.z, position.x, position.y, position.z)
if (tempDist < dist) then
dist = tempDist
closestVehicle = vehicles[i]
end
end
if (closestVehicle ~= nil and DoesEntityExist(closestVehicle)) then
return closestVehicle
else
return nil
end
end
-- Returns all loaded vehicles on client side
function GetAllVehicles()
local vehicles = {}
for vehicle in EnumerateVehicles() do
table.insert(vehicles, vehicle)
end
return vehicles
end
function IsVehicleBlacklisted(vehicle)
-- check class
local class = GetVehicleClass(vehicle)
for i = 1, #Config.classesBlacklist, 1 do
if (class == Config.classesBlacklist[i]) then
return true
end
end
-- check model
local modelHash = GetEntityModel(vehicle)
for i = 1, #Config.vehiclesBlacklist, 1 do
if (modelHash == Config.vehiclesBlacklist[i]) then
return true
end
end
return false
end
function GetVehiclePosition(plate)
if (GetResourceState("kimi_callbacks") ~= "started") then
Log("^1[ERROR] \"kimi_callbacks\" is either not installed properly or hasn't been started!")
Log("^1[ERROR] The export \"GetVehiclePosition\" can only be used when having this resource installed.")
return nil
end
if (plate == nil) then
Log("^1[ERROR] \"plate\" was nil while trying to call export \"GetVehiclePosition\"!")
return nil
end
if (type(plate) ~= "string") then
Log("^1[ERROR] \"plate\" (\"" .. plate .. "\") needs to be of type string when calling export \"GetVehiclePosition\"!")
return nil
end
local position = exports["kimi_callbacks"]:Trigger("AP:getVehiclePosition", plate:upper())
return position
end
function GetVehiclePositions(...)
if (GetResourceState("kimi_callbacks") ~= "started") then
Log("^1[ERROR] \"kimi_callbacks\" is either not installed properly or hasn't been started!")
Log("^1[ERROR] The export \"GetVehiclePositions\" can only be used when having this resource installed.")
return nil
end
local plates = { ... }
for i, plate in ipairs(plates) do
if (plate == nil) then
Log("^1[ERROR] \"plate\" (Index: \"" .. i .. "\") was nil while trying to call export \"GetVehiclePositions\"!")
return nil
end
if (type(plate) ~= "string") then
Log("^1[ERROR] \"plate\" (\"" .. plate .. "\") (Index: " .. i .. ") needs to be of type string when calling export \"GetVehiclePositions\"!")
return nil
end
plates[i] = plates[i]:upper()
end
local data = exports["kimi_callbacks"]:Trigger("AP:getVehiclePositions", plates)
return data
end
-- getting all vehicles
function EnumerateVehicles()
return EnumerateEntities(FindFirstVehicle, FindNextVehicle, EndFindVehicle)
end
function EnumerateEntities(initFunc, moveFunc, disposeFunc)
return coroutine.wrap(function()
local iter, id = initFunc()
if not id or id == 0 then
disposeFunc(iter)
return
end
local enum = {handle = iter, destructor = disposeFunc}
setmetatable(enum, entityEnumerator)
local next = true
repeat
coroutine.yield(id)
next, id = moveFunc(iter)
until not next
enum.destructor, enum.handle = nil, nil
disposeFunc(iter)
end)
end
local entityEnumerator = {
__gc = function(enum)
if enum.destructor and enum.handle then
enum.destructor(enum.handle)
end
enum.destructor = nil
enum.handle = nil
end
}

View file

@ -0,0 +1,210 @@
local enabled = true
Citizen.CreateThread(function()
while (true) do
local playerPed = PlayerPedId()
if (DoesEntityExist(playerPed)) then
TriggerServerEvent("mh_Parking:syncPlayerPosition", GetEntityCoords(playerPed))
end
Citizen.Wait(3000)
end
end)
Citizen.CreateThread(function()
while (true) do
Citizen.Wait(5 * 60 * 1000)
local vehicle = GetVehiclePedIsIn(PlayerPedId())
if (DoesEntityExist(vehicle)) then
TriggerEvent("mh_Parking:updateVehicle", vehicle)
end
end
end)
Citizen.CreateThread(function()
Citizen.Wait(3000)
local isInVehicle = false
local currentVehiclePlate = nil
local wasDead = false
while (true) do
Citizen.Wait(50)
local playerPed = PlayerPedId()
if (not isInVehicle and IsPedInAnyVehicle(playerPed)) then
-- entered vehicle
isInVehicle = true
if (enabled) then
local vehicle = GetVehiclePedIsIn(playerPed, false)
if (NetworkGetEntityIsNetworked(vehicle) and not IsVehicleBlacklisted(vehicle)) then
local networkId = NetworkGetNetworkIdFromEntity(vehicle)
SetNetworkIdCanMigrate(netId, true)
local modifications = GetVehicleModifications(vehicle)
currentVehiclePlate = GetVehicleNumberPlateText(vehicle)
TriggerServerEvent("mh_Parking:enteredVehicle", networkId, currentVehiclePlate, modifications)
Log("Vehicle " .. currentVehiclePlate .. " entered")
end
end
elseif (isInVehicle and not IsPedInAnyVehicle(playerPed, false)) then
-- left vehicle
isInVehicle = false
if (enabled and not wasDead) then
local vehicle = GetVehiclePedIsIn(playerPed, true)
if (vehicle ~= 0) then
if (NetworkGetEntityIsNetworked(vehicle) and not IsVehicleBlacklisted(vehicle)) then
local networkId = NetworkGetNetworkIdFromEntity(vehicle)
SetNetworkIdCanMigrate(netId, true)
local modifications = GetVehicleModifications(vehicle)
TriggerServerEvent("mh_Parking:leftVehicle", networkId, modifications)
Log("Vehicle " .. GetVehicleNumberPlateText(vehicle) .. " left")
-- if vehicle is deleted soon after
Citizen.CreateThread(function()
local veh = vehicle
local plate = GetVehicleNumberPlateText(veh)
local startTime = GetGameTimer()
local playerPed = PlayerPedId()
local playerPos
while (true) do
Citizen.Wait(0)
if (GetGameTimer() - startTime >= 1000) then
break
end
if (not DoesEntityExist(veh)) then
TriggerServerEvent("mh_Parking:deleteVehicle", plate, false)
Log("Vehicle " .. currentVehiclePlate .. " left and deleted after exit")
currentVehiclePlate = nil
break
end
playerPed = PlayerPedId()
playerPos = GetEntityCoords(playerPed)
vehPos = GetEntityCoords(veh)
if (Vdist(playerPos.x, playerPos.y, playerPos.z, vehPos.x, vehPos.y, vehPos.z) > 50.0) then
currentVehiclePlate = nil
break
end
end
currentVehiclePlate = nil
end)
end
elseif (currentVehiclePlate) then
TriggerServerEvent("mh_Parking:deleteVehicle", currentVehiclePlate, false)
Log("Vehicle " .. currentVehiclePlate .. " left and deleted")
currentVehiclePlate = nil
end
end
end
if (not wasDead and IsPlayerDead(PlayerId())) then
wasDead = true
elseif (wasDead and not IsPlayerDead(PlayerId())) then
Citizen.CreateThread(function()
Citizen.Wait(1000)
wasDead = false
end)
end
end
end)
RegisterNetEvent("mh_Parking:setVehicleMods")
AddEventHandler("mh_Parking:setVehicleMods", function(netId, plate, modifications)
if (not IsModelInCdimage(modifications[1])) then
Log("Setting mods for " .. plate .. " failed. The model does NOT exist.")
TriggerServerEvent("mh_Parking:modelDoesNotExist", plate)
return
end
local timer = GetGameTimer()
while (not NetworkDoesEntityExistWithNetworkId(netId)) do
Citizen.Wait(0)
if (GetGameTimer() - 10000 > timer) then
Log("Setting mods for " .. plate .. " failed after 10s")
TriggerServerEvent("mh_Parking:setVehicleModsFailed", plate)
return
end
end
local vehicle = NetworkGetEntityFromNetworkId(netId)
if (DoesEntityExist(vehicle) and NetworkHasControlOfEntity(vehicle)) then
Log("Setting modifications for vehicle " .. plate)
SetVehicleModifications(vehicle, plate, modifications)
TriggerServerEvent("mh_Parking:setVehicleModsSuccess", plate)
else
Log("Setting mods failed for vehicle " .. plate .. ". Vehicle does not exist")
TriggerServerEvent("mh_Parking:setVehicleModsFailed", plate)
end
end)
AddEventHandler("mh_Parking:updateVehicle", function(vehicle)
if (vehicle == nil) then
Log("^1[ERROR] \"vehicle\" was nil while trying to update a vehicle!")
return
end
if (DoesEntityExist(vehicle) and NetworkGetEntityIsNetworked(vehicle) and not IsVehicleBlacklisted(vehicle)) then
Log("Triggering manual update of vehicle")
local networkId = NetworkGetNetworkIdFromEntity(vehicle)
local modifications = GetVehicleModifications(vehicle)
TriggerServerEvent("mh_Parking:updateVehicle", networkId, modifications)
end
end)
RegisterNetEvent("mh_Parking:enable")
AddEventHandler("mh_Parking:enable", function(enable)
if (enable == nil) then
Log("^1[ERROR] \"enable\" was nil while trying to enable/disable script!")
return
end
enabled = enable
if (enabled) then
Log("AdvancedParking enabled")
else
Log("AdvancedParking disabled")
end
end)
RegisterNetEvent("mh_Parking:notification")
AddEventHandler("mh_Parking:notification", function(msg)
SetNotificationTextEntry('STRING')
AddTextComponentSubstringPlayerName(msg)
DrawNotification(false, true)
end)
RegisterNetEvent("mh_Parking:renderScorched")
AddEventHandler("mh_Parking:renderScorched", function(vehicleNetId, scorched)
local vehicle = NetworkGetEntityFromNetworkId(vehicleNetId)
if (DoesEntityExist(vehicle)) then
SetEntityRenderScorched(vehicle, scorched)
end
end)

View file

@ -0,0 +1,64 @@
Config = {}
Config.exports = "lc_fuel" -- LegacyFuel
-- set this to true if you want to see debug messages on client and server side
Config.isDebug = false
-- this controls the distance at which vehicles will spawn to the closest player
-- (in meters)
Config.spawnDistance = 200.0
-- this controls when a vehicle will be removed from the database table when
-- calling the cleanup function (in hours; so 24 * 7 = one week)
Config.cleanUpThresholdTime = 24 * 7
-- set this to false if you do not want entities render as scorched when they
-- are completely broken
Config.renderScorched = true
-- vehicle classes that you do not want to save go here (remove the -- in front
-- of the number if you want it blacklisted)
Config.classesBlacklist = {
-- 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
}
-- other vehicles that you do not want to save can be inserted here (use `MODELNAME`
-- when you put them in there)
Config.vehiclesBlacklist = {
--`blista`,
}
-- this lets you control if vehicles should be deleted (in minutes; 0 if you do not
-- want to use it; this is useful for large servers with a lot of players)
Config.deleteTimer = 0
-- despawns all vehicles that are more than x meters away from a player
Config.deleteDistance = 25.0
-- when the notifications should be shown before the despawning
-- needs to be in descending order in minutes and lower than Config.deleteTimer
Config.deleteNotificationTimes = { 5, 3, 2, 1 }
-- notification to show players before deleting vehicles
-- (use %s as placeholder for time left in minutes)
Config.timeLeftNotification = "Fahrzeuge Despawn in %s Minuten."
-- notification to show players when deleting vehicles
Config.deleteNotification = "Lösche Fahrzeuge..."

View file

@ -0,0 +1,24 @@
fx_version 'cerulean'
games { 'gta5' }
author 'Mîhó'
description 'Park all your vehicles anywhere!'
version '1.0.0'
server_scripts {
'@oxmysql/lib/MySQL.lua',
'config.lua',
'sv_utils.lua',
'server.lua'
}
client_scripts {
'config.lua',
'cl_utils.lua',
'client.lua'
}
exports {
'GetVehiclePosition',
'GetVehiclePositions'
}

View file

@ -0,0 +1,32 @@
-- --------------------------------------------------------
-- Host: 127.0.0.1
-- Server version: 10.1.28-MariaDB - mariadb.org binary distribution
-- Server OS: Win32
-- HeidiSQL Version: 11.0.0.5919
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
-- Dumping structure for table fivem_testserver.vehicle_parking
CREATE TABLE IF NOT EXISTS `vehicle_parking` (
`plate` varchar(8) NOT NULL,
`modifications` text NOT NULL,
`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',
PRIMARY KEY (`plate`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Data exporting was unselected.
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;

View file

@ -0,0 +1,770 @@
local vehicles = {}
local DB_vehicles = {}
-- Command to delete ALL vehicles from the database table. Needs to be executed twice for security reason.
local deleteSavedVehicles = false
RegisterCommand("deleteSavedVehicles", function(source, args, raw)
if (deleteSavedVehicles) then
MySQL.query("DELETE FROM vehicle_parking", { })
vehicles = {}
Log("Deleted all vehicles from the vehicle_parking table.")
else
Log("Are you sure that you want to delete all vehicles from the parking list?\nIf yes, type the command a second time!")
end
deleteSavedVehicles = not deleteSavedVehicles
end, true)
-- read all vehicles from the database on startup and do a cleanup check
MySQL.ready(function()
-- fetch all database results
MySQL.query("SELECT plate, posX, posY, posZ, rotX, rotY, rotZ, modifications, lastUpdate FROM vehicle_parking", {}, function(results)
Log("Found " .. #results .. " saved vehicles in vehicle_parking.")
for i = 1, #results do
vehicles[results[i].plate] = {
handle = nil,
position = vector3(results[i].posX, results[i].posY, results[i].posZ),
rotation = vector3(results[i].rotX, results[i].rotY, results[i].rotZ),
modifications = json.decode(results[i].modifications),
lastUpdate = results[i].lastUpdate,
spawning = false,
spawningPlayer = nil,
faultyModel = false
}
end
--CleanUp()
end)
end)
-- Default CleanUp function. Deletes vehicles from the database after a specific amount of time has passed (configurable in config)
function CleanUp()
local currentTime = os.time()
local threshold = 60 * 60 * Config.cleanUpThresholdTime
local toDelete = {}
for plate, vehicle in pairs(vehicles) do
if (vehicle.lastUpdate < os.difftime(currentTime, threshold)) then
MySQL.query("DELETE FROM vehicle_parking WHERE plate = @plate",
{
["@plate"] = plate
})
table.insert(toDelete, plate)
end
end
for i = 1, #toDelete, 1 do
local plate = toDelete[i]
vehicles[plate] = nil
end
Log("CleanUp complete. Deleted " .. #toDelete .. " entries.")
end
-- loop to spawn vehicles near players
Citizen.CreateThread(function()
while (true) do
Citizen.Wait(5000)
if (GetActivePlayerCount() > 0) then
TrySpawnVehicles()
end
end
end)
Citizen.CreateThread(function()
while (true) do
CallDB_Vehicles()
Citizen.Wait(60000 * 5)
end
end)
-- loop to delete vehicles
Citizen.CreateThread(function()
if (Config.deleteTimer <= 0) then
return
end
while (true) do
Citizen.Wait(60000 * (Config.deleteTimer - Config.deleteNotificationTimes[1]))
TriggerClientEvent("mh_Parking:notification", -1, string.format(Config.timeLeftNotification, Config.deleteNotificationTimes[1]))
for i = 2, #Config.deleteNotificationTimes, 1 do
Citizen.Wait(60000 * (Config.deleteNotificationTimes[i - 1] - Config.deleteNotificationTimes[i]))
TriggerClientEvent("mh_Parking:notification", -1, string.format(Config.timeLeftNotification, Config.deleteNotificationTimes[i]))
end
Citizen.Wait(60000 * Config.deleteNotificationTimes[#Config.deleteNotificationTimes])
DeleteAllVehicles()
end
end)
-- sync player position
RegisterServerEvent("mh_Parking:syncPlayerPosition")
AddEventHandler("mh_Parking:syncPlayerPosition", function(position)
activePlayerPositions[source] = position
end)
-- player disconnected
AddEventHandler("playerDropped", function(disconnectReason)
activePlayerPositions[source] = nil
end)
-- player entered a vehicle
RegisterServerEvent("mh_Parking:enteredVehicle")
AddEventHandler("mh_Parking:enteredVehicle", function(networkId, plate, modifications)
local vehicle = NetworkGetEntityFromNetworkId(networkId)
if (DoesEntityExist(vehicle)) then
local currentTime = os.time()
local position = GetEntityCoords(vehicle)
position = vector3(math.floor(position.x * 100.0) / 100.0, math.floor(position.y * 100.0) / 100.0, math.floor(position.z * 100.0) / 100.0)
local rotation = GetEntityRotation(vehicle)
rotation = vector3(math.floor(rotation.x * 100.0) / 100.0, math.floor(rotation.y * 100.0) / 100.0, math.floor(rotation.z * 100.0) / 100.0)
if (vehicles[plate]) then
-- already on server list
Log("Entered vehicle " .. plate .. ". Updating...")
if (vehicles[plate].handle ~= vehicle) then
if (DoesEntityExist(vehicles[plate].handle)) then
DeleteEntity(vehicles[plate].handle)
end
vehicles[plate].handle = vehicle
end
vehicles[plate].position = position
vehicles[plate].rotation = rotation
vehicles[plate].modifications = modifications
vehicles[plate].lastUpdate = currentTime
MySQL.query("UPDATE vehicle_parking SET posX = @posX, posY = @posY, posZ = @posZ, rotX = @rotX, rotY = @rotY, rotZ = @rotZ, modifications = @modifications, lastUpdate = @lastUpdate WHERE plate = @plate", {
["@plate"] = plate,
["@posX"] = vehicles[plate].position.x,
["@posY"] = vehicles[plate].position.y,
["@posZ"] = vehicles[plate].position.z,
["@rotX"] = vehicles[plate].rotation.x,
["@rotY"] = vehicles[plate].rotation.y,
["@rotZ"] = vehicles[plate].rotation.z,
["@modifications"] = json.encode(vehicles[plate].modifications),
["@lastUpdate"] = vehicles[plate].lastUpdate
})
else
-- insert in db
Log("Entered vehicle " .. plate .. ". Inserting new...")
vehicles[plate] = {
handle = vehicle,
position = position,
rotation = rotation,
modifications = modifications,
lastUpdate = currentTime,
spawning = false,
spawningPlayer = nil
}
MySQL.query('SELECT * FROM player_vehicles WHERE plate = @plate', { ['@plate'] = plate }, function(rs)
Log(json.encode(rs))
if rs[1] then
Log("FOUND OWNER FROM VEHICLE")
MySQL.query("INSERT INTO vehicle_parking (plate, posX, posY, posZ, rotX, rotY, rotZ, modifications, lastUpdate) VALUES (@plate, @posX, @posY, @posZ, @rotX, @rotY, @rotZ, @modifications, @lastUpdate)", {
["@plate"] = plate,
["@posX"] = vehicles[plate].position.x,
["@posY"] = vehicles[plate].position.y,
["@posZ"] = vehicles[plate].position.z,
["@rotX"] = vehicles[plate].rotation.x,
["@rotY"] = vehicles[plate].rotation.y,
["@rotZ"] = vehicles[plate].rotation.z,
["@modifications"] = json.encode(vehicles[plate].modifications),
["@lastUpdate"] = vehicles[plate].lastUpdate
})
else
Log("1: NO VEHICLE FOUND")
end
end)
end
else
Log("Vehicle does not exist. Might be a Onesync error.")
end
end)
-- player left a vehicle
RegisterServerEvent("mh_Parking:leftVehicle")
AddEventHandler("mh_Parking:leftVehicle", function(networkId, modifications)
local vehicle = NetworkGetEntityFromNetworkId(networkId)
if (DoesEntityExist(vehicle)) then
local currentTime = os.time()
local plate = GetVehicleNumberPlateText(vehicle)
local position = GetEntityCoords(vehicle)
position = vector3(math.floor(position.x * 100.0) / 100.0, math.floor(position.y * 100.0) / 100.0, math.floor(position.z * 100.0) / 100.0)
local rotation = GetEntityRotation(vehicle)
rotation = vector3(math.floor(rotation.x * 100.0) / 100.0, math.floor(rotation.y * 100.0) / 100.0, math.floor(rotation.z * 100.0) / 100.0)
if (vehicles[plate]) then
-- already on server list
Log("Left vehicle " .. plate .. ". Updating...")
if (vehicles[plate].handle ~= vehicle) then
if (DoesEntityExist(vehicles[plate].handle)) then
DeleteEntity(vehicles[plate].handle)
end
vehicles[plate].handle = vehicle
end
vehicles[plate].position = position
vehicles[plate].rotation = rotation
vehicles[plate].modifications = modifications
vehicles[plate].lastUpdate = currentTime
MySQL.query("UPDATE vehicle_parking SET posX = @posX, posY = @posY, posZ = @posZ, rotX = @rotX, rotY = @rotY, rotZ = @rotZ, modifications = @modifications, lastUpdate = @lastUpdate WHERE plate = @plate",{
["@plate"] = plate,
["@posX"] = vehicles[plate].position.x,
["@posY"] = vehicles[plate].position.y,
["@posZ"] = vehicles[plate].position.z,
["@rotX"] = vehicles[plate].rotation.x,
["@rotY"] = vehicles[plate].rotation.y,
["@rotZ"] = vehicles[plate].rotation.z,
["@modifications"] = json.encode(vehicles[plate].modifications),
["@lastUpdate"] = currentTime
})
else
-- insert in db
Log("Left vehicle " .. plate .. ". Inserting new...")
vehicles[plate] = {
handle = vehicle,
position = position,
rotation = rotation,
modifications = modifications,
lastUpdate = currentTime,
spawning = false,
spawningPlayer = nil
}
MySQL.query('SELECT * FROM player_vehicles WHERE plate = @plate', { ['@plate'] = plate }, function(rs)
if rs[1] then
Log("FOUND OWNER FROM VEHICLE")
MySQL.query("INSERT INTO vehicle_parking (plate, posX, posY, posZ, rotX, rotY, rotZ, modifications, lastUpdate) VALUES (@plate, @posX, @posY, @posZ, @rotX, @rotY, @rotZ, @modifications, @lastUpdate)",{
["@plate"] = plate,
["@posX"] = vehicles[plate].position.x,
["@posY"] = vehicles[plate].position.y,
["@posZ"] = vehicles[plate].position.z,
["@rotX"] = vehicles[plate].rotation.x,
["@rotY"] = vehicles[plate].rotation.y,
["@rotZ"] = vehicles[plate].rotation.z,
["@modifications"] = json.encode(vehicles[plate].modifications),
["@lastUpdate"] = vehicles[plate].lastUpdate
})
else
Log("2: NO VEHICLE FOUND")
end
end)
end
end
end)
-- update a vehicle with its modifications
RegisterServerEvent("mh_Parking:updateVehicle")
AddEventHandler("mh_Parking:updateVehicle", function(networkId, modifications)
local vehicle = NetworkGetEntityFromNetworkId(networkId)
if (DoesEntityExist(vehicle)) then
local currentTime = os.time()
local plate = GetVehicleNumberPlateText(vehicle)
local position = GetEntityCoords(vehicle)
position = vector3(math.floor(position.x * 100.0) / 100.0, math.floor(position.y * 100.0) / 100.0, math.floor(position.z * 100.0) / 100.0)
local rotation = GetEntityRotation(vehicle)
rotation = vector3(math.floor(rotation.x * 100.0) / 100.0, math.floor(rotation.y * 100.0) / 100.0, math.floor(rotation.z * 100.0) / 100.0)
if (vehicles[plate] ~= nil) then
-- already on server list
Log("Updating vehicle " .. plate)
if (vehicles[plate].handle ~= vehicle) then
if (DoesEntityExist(vehicles[plate].handle)) then
DeleteEntity(vehicles[plate].handle)
end
vehicles[plate].handle = vehicle
end
vehicles[plate].position = position
vehicles[plate].rotation = rotation
vehicles[plate].modifications = modifications
vehicles[plate].lastUpdate = currentTime
MySQL.query("UPDATE vehicle_parking SET posX = @posX, posY = @posY, posZ = @posZ, rotX = @rotX, rotY = @rotY, rotZ = @rotZ, modifications = @modifications, lastUpdate = @lastUpdate WHERE plate = @plate",{
["@plate"] = plate,
["@posX"] = vehicles[plate].position.x,
["@posY"] = vehicles[plate].position.y,
["@posZ"] = vehicles[plate].position.z,
["@rotX"] = vehicles[plate].rotation.x,
["@rotY"] = vehicles[plate].rotation.y,
["@rotZ"] = vehicles[plate].rotation.z,
["@modifications"] = json.encode(vehicles[plate].modifications),
["@lastUpdate"] = vehicles[plate].lastUpdate
})
else
-- insert in db
Log("Updating vehicle " .. plate .. ". Inserting new...")
vehicles[plate] = {
handle = vehicle,
position = position,
rotation = rotation,
modifications = modifications,
lastUpdate = currentTime,
spawning = false,
spawningPlayer = nil
}
MySQL.query('SELECT * FROM player_vehicles WHERE plate = @plate', { ['@plate'] = plate }, function(rs)
if rs[1] then
Log("FOUND OWNER FROM VEHICLE")
MySQL.query("INSERT INTO vehicle_parking (plate, posX, posY, posZ, rotX, rotY, rotZ, modifications, lastUpdate) VALUES (@plate, @posX, @posY, @posZ, @rotX, @rotY, @rotZ, @modifications, @lastUpdate)",{
["@plate"] = plate,
["@posX"] = vehicles[plate].position.x,
["@posY"] = vehicles[plate].position.y,
["@posZ"] = vehicles[plate].position.z,
["@rotX"] = vehicles[plate].rotation.x,
["@rotY"] = vehicles[plate].rotation.y,
["@rotZ"] = vehicles[plate].rotation.z,
["@modifications"] = json.encode(vehicles[plate].modifications),
["@lastUpdate"] = vehicles[plate].lastUpdate
})
else
Log("3: NO VEHICLE FOUND")
end
end)
end
end
end)
-- delete a vehicle from the table (and world) with its plate
RegisterServerEvent("mh_Parking:deleteVehicle")
AddEventHandler("mh_Parking:deleteVehicle", function(plate, deleteEntity)
if (plate == nil) then
Log("^1mh_Parking: \"plate\" was nil while trying to delete vehicle!")
return
end
Log("Trying to delete " .. tostring(plate))
if (vehicles[plate] ~= nil) then
if (deleteEntity and DoesEntityExist(vehicles[plate].handle)) then
DeleteEntity(vehicles[plate].handle)
end
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_parking WHERE plate = @plate",
{
["@plate"] = plate
})
else
local loadedVehicles = GetAllVehicles()
local loadedVehicle = TryGetLoadedVehicle(plate, loadedVehicles)
if (loadedVehicle ~= nil) then
if (deleteEntity and DoesEntityExist(loadedVehicle)) then
DeleteEntity(loadedVehicle)
end
MySQL.query("DELETE FROM vehicle_parking WHERE plate = @plate",
{
["@plate"] = plate
})
end
end
end)
-- checks if vehicles have to be spawned and spawns them if necessary
function TrySpawnVehicles()
local loadedVehicles = GetAllVehicles()
local playerVehiclePlates = {}
for id, position in pairs(activePlayerPositions) do
local ped = GetPlayerPed(id)
local veh = GetVehiclePedIsIn(ped, false)
if (DoesEntityExist(veh)) then
table.insert(playerVehiclePlates, GetVehicleNumberPlateText(veh))
end
end
-- check, if vehicles need to be spawned
for plate, vehicleData in pairs(vehicles) do
local plateCheck = CheckPlate(plate)
if (plateCheck ~= false) then
if (not vehicleData.faultyModel) then
if (not vehicleData.spawning) then
local closestPlayer, dist = GetClosestPlayerId(vehicleData.position)
if (closestPlayer ~= nil and dist < Config.spawnDistance and not ContainsPlate(plate, playerVehiclePlates)) then
if (vehicleData.handle ~= nil and DoesEntityExist(vehicleData.handle)) then
-- vehicle found on server side
UpdateVehicle(plate)
else
-- vehicle not found on server side
-- check, if it is loaded differently
local loadedVehicle = TryGetLoadedVehicle(plate, loadedVehicles)
if (loadedVehicle ~= nil) then
-- vehicle found
vehicleData.handle = loadedVehicle
UpdateVehicle(plate)
else
-- vehicle not found
-- try and spawn it
local playerId, distance = GetClosestPlayerId(vehicleData.position)
if (playerId and distance < Config.spawnDistance) then
vehicleData.spawning = true
Citizen.CreateThread(function()
local vec4 = vector4(vehicleData.position.x, vehicleData.position.y, vehicleData.position.z, vehicleData.rotation.z)
local vehicle = Citizen.InvokeNative(GetHashKey("CREATE_AUTOMOBILE"), vehicleData.modifications[1], vec4.xyzw)
while (not DoesEntityExist(vehicle)) do
Citizen.Wait(0)
end
SetEntityCoords(vehicle, vehicleData.position.x, vehicleData.position.y, vehicleData.position.z)
SetEntityRotation(vehicle, vehicleData.rotation.x, vehicleData.rotation.y, vehicleData.rotation.z)
vehicleData.handle = vehicle
local networkOwner = -1
while (networkOwner == -1) do
Citizen.Wait(0)
networkOwner = NetworkGetEntityOwner(vehicleData.handle)
end
vehicleData.spawningPlayer = GetPlayerIdentifiersSorted(networkOwner)["license"]
Log("Creating vehicle " .. plate .. " at " .. tostring(vehicleData.position) .. " at entity owner " .. tostring(networkOwner))
TriggerClientEvent("mh_Parking:setVehicleMods", networkOwner, NetworkGetNetworkIdFromEntity(vehicleData.handle), plate, vehicleData.modifications)
end)
end
end
end
end
elseif (vehicleData.spawningPlayer) then
-- if vehicle is currently spawning check if responsible player is still connected
if (not IsPlayerWithLicenseActive(vehicleData.spawningPlayer)) then
TriggerEvent("mh_Parking:setVehicleModsFailed", plate)
end
end
end
end
end
end
RegisterServerEvent("mh_Parking:setVehicleModsSuccess")
AddEventHandler("mh_Parking:setVehicleModsSuccess", function(plate)
Log("Setting mods successful...")
if (vehicles[plate]) then
vehicles[plate].spawning = false
vehicles[plate].spawningPlayer = nil
end
end)
RegisterServerEvent("mh_Parking:setVehicleModsFailed")
AddEventHandler("mh_Parking:setVehicleModsFailed", function(plate)
Log("Setting mods for " .. plate .. " failed... retrying...")
if (vehicles[plate] and vehicles[plate].handle and DoesEntityExist(vehicles[plate].handle)) then
local networkOwner = -1
while (networkOwner == -1) do
Citizen.Wait(0)
networkOwner = NetworkGetEntityOwner(vehicles[plate].handle)
end
local netOwnerLicense = GetPlayerIdentifiersSorted(networkOwner)["license"]
if (netOwnerLicense ~= nil) then
vehicles[plate].spawningPlayer = GetPlayerIdentifiersSorted(networkOwner)["license"]
TriggerClientEvent("mh_Parking:setVehicleMods", networkOwner, NetworkGetNetworkIdFromEntity(vehicles[plate].handle), plate, vehicles[plate].modifications)
else
Log("Something went wrong while trying to get player license for spawning vehicle " .. tostring(plate) .. ". Despawning vehicle")
DeleteEntity(vehicles[plate].handle)
vehicles[plate].handle = nil
vehicles[plate].spawningPlayer = nil
vehicles[plate].spawning = false
end
end
end)
RegisterServerEvent("mh_Parking:modelDoesNotExist")
AddEventHandler("mh_Parking:modelDoesNotExist", function(plate)
if (vehicles[plate] == nil) then
return
end
if (DoesEntityExist(vehicles[plate].handle)) then
DeleteEntity(vehicles[plate].handle)
end
vehicles[plate].handle = nil
vehicles[plate].spawningPlayer = nil
vehicles[plate].spawning = false
Log("The model for vehicle " .. tostring(plate) .. " does not exist. Removing from spawn pool...")
vehicles[plate].faultyModel = true
end)
RegisterServerEvent("mh_Parking:updatePlate")
AddEventHandler("mh_Parking:updatePlate", function(oldPlate, newPlate)
if (oldPlate == nil or newPlate == nil) then
Log("^1mh_Parking: \"oldPlate\" or \"newPlate\" was nil while trying to update a plate!")
return
end
if (vehicles[oldPlate]) then
if (vehicles[newPlate]) then
Log("Updating plate failed. New plate was already there!")
else
vehicles[newPlate] = vehicles[oldPlate]
vehicles[oldPlate] = nil
Log("Updating plate successful!")
MySQL.query("UPDATE vehicle_parking SET plate = @newPlate WHERE plate = @oldPlate",
{
["@newPlate"] = newPlate,
["@oldPlate"] = oldPlate
})
end
else
Log("Updating plate failed or unnecessary. Old plate was not found!")
end
end)
function UpdateVehicle(plate)
local newPos = GetEntityCoords(vehicles[plate].handle)
local newRot = GetEntityRotation(vehicles[plate].handle)
newPos = vector3(math.floor(newPos.x * 100.0) * 0.01, math.floor(newPos.y * 100.0) * 0.01, math.floor(newPos.z * 100.0) * 0.01)
newRot = vector3(math.floor(newRot.x * 100.0) * 0.01, math.floor(newRot.y * 100.0) * 0.01, math.floor(newRot.z * 100.0) * 0.01)
local newLockStatus = GetVehicleDoorLockStatus(vehicles[plate].handle)
local newEngineHealth = math.floor(GetVehicleEngineHealth(vehicles[plate].handle) * 10.0) * 0.1
local newTankHealth = math.floor(GetVehiclePetrolTankHealth(vehicles[plate].handle) * 10.0) * 0.1
if (Vector3DistFast(vehicles[plate].position, newPos) > 1.0
or GetRotationDifference(vehicles[plate].rotation, newRot) > 15.0
or newLockStatus ~= vehicles[plate].modifications[2]
or math.abs(newEngineHealth - vehicles[plate].modifications[5]) > 5.0
or math.abs(newTankHealth - vehicles[plate].modifications[6]) > 5.0
) then
vehicles[plate].modifications[2] = newLockStatus
vehicles[plate].modifications[5] = newEngineHealth
vehicles[plate].modifications[6] = newTankHealth
Log("Updating vehicle " .. plate)
vehicles[plate].position = newPos
vehicles[plate].rotation = newRot
MySQL.query("UPDATE vehicle_parking SET posX = @posX, posY = @posY, posZ = @posZ, rotX = @rotX, rotY = @rotY, rotZ = @rotZ, modifications = @modifications WHERE plate = @plate",
{
["@posX"] = vehicles[plate].position.x,
["@posY"] = vehicles[plate].position.y,
["@posZ"] = vehicles[plate].position.z,
["@rotX"] = vehicles[plate].rotation.x,
["@rotY"] = vehicles[plate].rotation.y,
["@rotZ"] = vehicles[plate].rotation.z,
["@modifications"] = json.encode(vehicles[plate].modifications),
["@plate"] = plate
})
end
end
function DeleteAllVehicles()
TriggerClientEvent("mh_Parking:notification", -1, Config.deleteNotification)
local peds = GetAllPeds()
local playerPeds = {}
for i = 1, #peds, 1 do
if (IsPedAPlayer(peds[i])) then
table.insert(playerPeds, peds[i])
end
end
if (#playerPeds == 0) then
return
end
local time = GetGameTimer()
local vehs = GetAllVehicles()
local deleted = 0
for i = 1, #vehs, 1 do
if (not IsAnyPlayerInsideVehicle(vehs[i], playerPeds)) then
local closestPlayer, distance = GetClosestPlayerPed(GetEntityCoords(vehs[i]), playerPeds)
if (closestPlayer ~= nil and distance > Config.deleteDistance) then
local plate = GetVehicleNumberPlateText(vehs[i])
if (vehicles[plate] ~= nil) then
vehicles[plate] = nil
MySQL.query("DELETE FROM vehicle_parking WHERE plate = @plate",
{
["@plate"] = plate
})
end
DeleteEntity(vehs[i])
deleted = deleted + 1
end
end
end
Log("Deleted " .. tostring(deleted) .. "/" .. tostring(#vehs) .. " vehicles. Took " .. tostring((GetGameTimer() - time) / 1000.0) .. "sec")
end
AddEventHandler("onResourceStop", function(name)
if (name ~= GetCurrentResourceName()) then
return
end
-- delete vehicles that are still being spawned before actually stopping the resource
for plate, vehicleData in pairs(vehicles) do
if (vehicleData.spawning and DoesEntityExist(vehicleData.handle)) then
DeleteEntity(vehicleData.handle)
end
end
end)
-- render entity scorched (trigger with netid of the vehicle and false when repairing)
RegisterServerEvent("mh_Parking:renderScorched")
AddEventHandler("mh_Parking:renderScorched", function(vehicleNetId, scorched)
local vehicleHandle = NetworkGetEntityFromNetworkId(vehicleNetId)
if (DoesEntityExist(vehicleHandle)) then
TriggerClientEvent("mh_Parking:renderScorched", -1, vehicleNetId, scorched)
end
end)
if (GetResourceState("kimi_callbacks") == "started") then
exports["kimi_callbacks"]:Register("AP:getVehiclePosition", function(source, plate)
local vehs = GetAllVehicles()
for i, veh in ipairs(vehs) do
local vehPlate = GetVehicleNumberPlateText(veh)
if (plate == vehPlate or plate == string.gsub(vehPlate, "^%s*(.-)%s*$", "%1")) then
return GetEntityCoords(veh)
end
end
for vehPlate, vehData in pairs(vehicles) do
if (plate == vehPlate or plate == string.gsub(vehPlate, "^%s*(.-)%s*$", "%1")) then
return vehData.position
end
end
return nil
end)
exports["kimi_callbacks"]:Register("AP:getVehiclePositions", function(source, plates)
local platePositions = {}
Log("Looking for plates:")
for i, plate in ipairs(plates) do
Log("\"" .. plate .. "\"")
end
local vehs = GetAllVehicles()
for i, veh in ipairs(vehs) do
local vehPlate = GetVehicleNumberPlateText(veh)
local trimmedVehPlate = string.gsub(vehPlate, "^%s*(.-)%s*$", "%1")
for j, plate in ipairs(plates) do
if (plate == vehPlate or plate == trimmedVehPlate) then
platePositions[plate] = GetEntityCoords(veh)
break
end
end
end
for i, plate in ipairs(plates) do
if (platePositions[plate] == nil) then
for vehPlate, vehData in pairs(vehicles) do
local trimmedVehPlate = string.gsub(vehPlate, "^%s*(.-)%s*$", "%1")
if (plate == vehPlate or plate == trimmedVehPlate) then
platePositions[plate] = vehData.position
break
end
end
end
end
return platePositions
end)
end
RegisterNetEvent('mh_Parking:removeVehicle')
AddEventHandler('mh_Parking:removeVehicle', function(plate)
print("mh_parking: "..plate)
vehicles[plate] = nil
end)
function CallDB_Vehicles()
DB_vehicles = {}
MySQL.query('SELECT * FROM player_vehicles', {}, function(rs)
for k, v in pairs(rs) do
table.insert(DB_vehicles, v)
end
end) --DB_vehicles
end
function CheckPlate(plate) -- DB_Players
for k, v in pairs(DB_vehicles) do
if Config.isDebug then
Log(json.encode(v))
Log(plate)
Log(v.plate)
end
if v.plate == plate then
return true
end
end
return false
end

View file

@ -0,0 +1,151 @@
activePlayerPositions = {}
-- returns a loaded vehicled with a given number plate
function TryGetLoadedVehicle(plate, loadedVehicles)
for i = 1, #loadedVehicles, 1 do
if (plate == GetVehicleNumberPlateText(loadedVehicles[i]) and DoesEntityExist(loadedVehicles[i])) then
return loadedVehicles[i]
end
end
return nil
end
function ContainsPlate(plate, vehiclePlates)
for i = 1, #vehiclePlates, 1 do
if (plate == vehiclePlates[i]) then
return true
end
end
return false
end
-- Logs text to the server console
function Log(text)
if (Config.isDebug) then
Print(GetCurrentResourceName() .. ": " .. text)
end
end
-- return the distance between two positions (Vector3)
function Vector3Dist(v1, v2)
return math.sqrt( (v2.x - v1.x) * (v2.x - v1.x) + (v2.y - v1.y) * (v2.y - v1.y) + (v2.z - v1.z) * (v2.z - v1.z) )
end
-- return the distance between two positions without sqrt (Vector3)
function Vector3DistFast(v1, v2)
return (v2.x - v1.x) * (v2.x - v1.x) + (v2.y - v1.y) * (v2.y - v1.y) + (v2.z - v1.z) * (v2.z - v1.z)
end
-- returns the difference in degrees from the axis with the highest difference
function GetRotationDifference(r1, r2)
local x = math.abs(r1.x - r2.x)
local y = math.abs(r1.y - r2.y)
local z = math.abs(r1.z - r2.z)
if (x > y and x > z) then
return x
elseif (y > z) then
return y
else
return z
end
end
-- get the amount of currently active players
function GetActivePlayerCount()
local playerCount = 0
for k, v in pairs(activePlayerPositions) do
playerCount = playerCount + 1
end
return playerCount
end
-- return the ID of the closest player
function GetClosestPlayerId(position)
local closestDistance = 1000000.0
local closestPlayerID = nil
local closestPos = nil
for playerID, pos in pairs(activePlayerPositions) do
local distance = Vector3DistFast(position, pos)
if (distance < closestDistance) then
closestDistance = distance
closestPlayerID = playerID
closestPos = pos
end
end
local distance = nil
if (closestPlayerID ~= nil) then
distance = Vector3Dist(position, closestPos)
end
return closestPlayerID, distance
end
function IsAnyPlayerInsideVehicle(vehicle, playerPeds)
for i = 1, #playerPeds, 1 do
local veh = GetVehiclePedIsIn(playerPeds[i], false)
if (DoesEntityExist(veh) and veh == vehicle) then
return true
end
end
return false
end
-- return the ped of the closest player
function GetClosestPlayerPed(position, playerPeds)
local closestDistance = 1000000.0
local closestPlayerPed = nil
local closestPos = nil
for k, playerPed in pairs(playerPeds) do
local pos = GetEntityCoords(playerPed)
local distance = Vector3DistFast(position, pos)
if (distance < closestDistance) then
closestDistance = distance
closestPlayerPed = playerPed
closestPos = pos
end
end
local distance = 0.0
if (closestPlayerPed ~= nil) then
distance = Vector3Dist(position, closestPos)
end
return closestPlayerPed, distance
end
-- returns true if a player is active on the server with the specified license
function IsPlayerWithLicenseActive(license)
for playerId, playerPos in pairs(activePlayerPositions) do
if (GetPlayerIdentifiersSorted(playerId)["license"] == license) then
return true
end
end
return false
end
-- Return an array with all identifiers - e.g. ids["license"] = license:xxxxxxxxxxxxxxxx
function GetPlayerIdentifiersSorted(playerServerId)
local ids = {}
local identifiers = GetPlayerIdentifiers(playerServerId)
for k, identifier in pairs (identifiers) do
local i, j = string.find(identifier, ":")
local idType = string.sub(identifier, 1, i-1)
ids[idType] = identifier
end
return ids
end

View file

@ -0,0 +1,5 @@
function Debug(text)
if Config.Debug then
print(text)
end
end

View file

@ -0,0 +1,180 @@
QBCore = exports['qb-core']:GetCoreObject()
Player = nil
npcHandle = nil
isNPCSpawned = false
CurrentZone = nil
CurrentActionData = {}
hasAlreadyEnteredMarker = false
Citizen.CreateThread(function()
while Player == nil do
Player = exports['qb-core']:GetPlayerData()
Wait(0)
end
end)
Citizen.CreateThread(function()
while true do
Wait(15000)
local ped = PlayerPedId()
if IsPedInAnyVehicle(ped, false) then
local veh = GetVehiclePedIsIn(ped, false)
local mods = QBCore.Functions.GetVehicleProperties(veh)
print("Triggert setMods: "..json.encode(mods))
TriggerServerEvent('mh_garage:setMods', mods)
end
end
end)
-- Funktion zum Spawnen des NPCs
function SpawnGuardNPC(npc)
-- Ped Model laden
RequestModel(npc.model)
local timeout = 0
while not HasModelLoaded(npc.model) and timeout < 100 do
timeout = timeout + 1
Wait(100)
end
if not HasModelLoaded(npc.model) then
return
end
-- NPC erstellen
npcHandle = CreatePed(4, npc.model, npc.spawn.x, npc.spawn.y, npc.spawn.z, npc.spawn.w, false, true)
if not DoesEntityExist(npcHandle) then
return
end
-- NPC Eigenschaften setzen
SetEntityAsMissionEntity(npcHandle, true, true)
SetBlockingOfNonTemporaryEvents(npcHandle, true)
SetPedDiesWhenInjured(npcHandle, false)
SetPedCanPlayAmbientAnims(npcHandle, true)
SetPedCanRagdollFromPlayerImpact(npcHandle, false)
SetEntityInvincible(npcHandle, true)
FreezeEntityPosition(npcHandle, true)
-- Optional: Animation für den NPC
TaskStartScenarioInPlace(npcHandle, "WORLD_HUMAN_GUARD_STAND", 0, true)
isNPCSpawned = true
end
-- Funktion zum Entfernen des NPCs
function RemoveGuardNPC()
if DoesEntityExist(npcHandle) then
DeleteEntity(npcHandle)
isNPCSpawned = false
end
end
-- Hauptthread zum Überprüfen der Spieler-Position
CreateThread(function()
while true do
Wait(0)
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local isInMarker = false
for k, v in pairs(Config.Zonen) do
local dist = #(coords - vector3(v.NPC.spawn.x, v.NPC.spawn.y, v.NPC.spawn.z))
local spawnDistance = v.NPC.distance
if dist <= spawnDistance then
isInMarker = true
CurrentZone = v
end
end
if isInMarker and not hasAlreadyEnteredMarker then
hasAlreadyEnteredMarker = true
SpawnGuardNPC(CurrentZone.NPC)
AddTargetOptions()
end
if not isInMarker and hasAlreadyEnteredMarker then
hasAlreadyEnteredMarker = false
CurrentZone = nil
exports['qb-target']:RemoveTargetEntity(npcHandle)
RemoveGuardNPC()
end
end
end)
function AddTargetOptions()
local opt = {
{
type = "client",
event = "mh_garage:storeVehicle",
icon = "fas fa-parking",
label = "Fahrzeug einparken",
},
{
type = "client",
event = "mh_garage:retrieveOwnerVehicle",
icon = "fas fa-car",
label = "Eigene Fahrzeug ausparken",
},
{
type = "client",
event = "mh_garage:retrieveVehicle",
icon = "key",
label = "Schlüssel Fahrzeug ausparken",
}
}
if Config.Verwaltung.garage then
table.insert(opt, {
type = "client",
event = "mh_garage:verwaltungVeh",
icon = "hand",
label = "Fahrzeuge Verwalten",
})
end
exports['qb-target']:AddTargetEntity(npcHandle, {
options = opt,
distance = 2.5
})
end
function Notification(text, type)
lib.notify({
title = "Garage - "..CurrentZone.name,
description = text,
type = type,
position = 'top',
})
end
---------------------------- NetEvents
RegisterNetEvent('mh_jobgarage:notify')
AddEventHandler('mh_jobgarage:notify', function(title, text, type)
Notification(text, type)
end)
function GetVehicleDamagePercentage(vehicle)
if not vehicle then return 0 end
-- Hole die verschiedenen Gesundheitswerte
local engineHealth = GetVehicleEngineHealth(vehicle)
local bodyHealth = GetVehicleBodyHealth(vehicle)
local tankHealth = GetVehiclePetrolTankHealth(vehicle)
-- Normalisiere die Werte (Standard-Maximalwerte: 1000.0)
local enginePercent = (engineHealth / 1000.0) * 100
local bodyPercent = (bodyHealth / 1000.0) * 100
local tankPercent = (tankHealth / 1000.0) * 100
-- Berechne Durchschnitt als Gesamtzustand
local totalHealth = (enginePercent + bodyPercent + tankPercent) / 3
-- Runde auf ganze Zahlen
return math.floor(totalHealth)
end

View file

@ -0,0 +1,152 @@
RegisterNetEvent('mh_garage:retrieveOwnerVehicle')
AddEventHandler('mh_garage:retrieveOwnerVehicle', function()
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local random = SelectName()
local opt = {}
QBCore.Functions.TriggerCallback('mh_garage:retrieveOwnerVehicle', function(cb)
if cb == false then
Notification("Es ist kein Fahrzeug hier!", "inform")
return
end
for i = 1, #cb, 1 do
local mods = json.decode(cb[i].mods)
table.insert(opt, {
title = cb[i].name,
description = "Kennzeichen: "..cb[i].plate, --[[ \nTankinhalt: "..math.round(mods.fuelLevel, 2).."%" ]]
icon = "car",
onSelect = function()
cb[i].mods = mods
SpawnThisVehicle(cb[i])
end
})
end
lib.registerContext({
id = "retrieveVehicle",
title = random.name,
options = opt
})
lib.showContext("retrieveVehicle")
end, CurrentZone.name)
end)
RegisterNetEvent('mh_garage:retrieveVehicle')
AddEventHandler('mh_garage:retrieveVehicle', function()
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local random = SelectName()
local opt = {}
QBCore.Functions.TriggerCallback('mh_garage:retrieveKeyVehicle', function(cb)
if cb == false then
Notification("Es ist kein Fahrzeug hier!", "inform")
return
end
for i = 1, #cb, 1 do
if cb[i].garage ~= "OUT" then
local mods = json.decode(cb[i].mods)
table.insert(opt, {
title = cb[i].name,
description = "Kennzeichen: "..cb[i].plate, --[[ \nTankinhalt: "..math.round(mods.fuelLevel, 2).."%" ]]
icon = "car",
onSelect = function()
cb[i].mods = mods
SpawnThisVehicle(cb[i])
end
})
end
end
lib.registerContext({
id = "retrieveVehicle",
title = random.name,
options = opt
})
lib.showContext("retrieveVehicle")
end, CurrentZone.name)
end)
function SpawnThisVehicle(vehicle)
local spawnPoint = nil
-- Freien Spawnpunkt suchen
for _, spot in pairs(CurrentZone.vehicle_spawn) do
if not IsAnyVehicleNearPoint(spot.x, spot.y, spot.z, 3.0) then
spawnPoint = spot
break
end
end
if spawnPoint then
QBCore.Functions.SpawnVehicle(vehicle.vehicle, function(veh)
-- Fahrzeug ID für Server
local netId = NetworkGetNetworkIdFromEntity(veh)
-- Grundeinstellungen
SetVehicleNumberPlateText(veh, vehicle.plate)
SetVehicleDoorsLocked(veh, 0)
SetEntityHeading(veh, spawnPoint.w)
-- Motor aus
SetVehicleEngineOn(veh, false, true, true)
-- Fahrzeug Eigenschaften
local mods = type(vehicle.mods) == 'string' and json.decode(vehicle.mods) or vehicle.mods
-- Grundwerte setzen
SetVehicleFuelLevel(veh, mods.fuelLevel)
exports["lc_fuel"]:SetFuel(veh, mods.fuelLevel)
SetVehicleEngineHealth(veh, mods.engineHealth)
SetVehicleBodyHealth(veh, mods.bodyHealth)
SetVehicleDirtLevel(veh, mods.dirtLevel)
-- Türen Status
if mods.doorStatus then
for doorIndex = 0, 5 do
if mods.doorStatus[tostring(doorIndex)] then
SetVehicleDoorBroken(veh, doorIndex, true)
end
end
end
-- Fenster Status
if mods.windowStatus then
for windowIndex = 0, 7 do
if not mods.windowStatus[tostring(windowIndex)] then
SmashVehicleWindow(veh, windowIndex)
end
end
end
-- Alle Mods anwenden
QBCore.Functions.SetVehicleProperties(veh, mods)
-- Fahrzeug auf den Boden setzen
SetVehicleOnGroundProperly(veh)
-- Server über gespawntes Fahrzeug informieren
TriggerServerEvent('mh_garage:spawnedVehicle', netId, vehicle.plate)
-- Optional: Erfolgsmeldung
lib.notify({
title = "Fahrzeug geparkt...",
description = "Dein Fahrzeug steht auf Parkplatz "..math.random(1, 15),
type = "success"
})
end, vector3(spawnPoint.x, spawnPoint.y, spawnPoint.z), true)
else
QBCore.Functions.Notify('Alle Parkplätze sind belegt!', 'error')
lib.notify({
title = "Fahrzeug nicht gefunden",
description = "Alle Parkplätze sind belegt!",
type = "success"
})
end
end

View file

@ -0,0 +1,131 @@
RegisterNetEvent('mh_garage:storeVehicle')
AddEventHandler('mh_garage:storeVehicle', function()
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local vehicles = GetGamePool('CVehicle')
local random = SelectName()
local opt = {}
for i = 1, #vehicles, 1 do
local veh_coords = GetEntityCoords(vehicles[i])
local distance = #(veh_coords - coords)
if distance < 15.0 then
local mods = QBCore.Functions.GetVehicleProperties(vehicles[i])
local lc_fuelLevel = exports["lc_fuel"]:GetFuel(vehicles[i]) -- Get the fuel level
mods.fuelLevel = lc_fuelLevel
table.insert(opt, {
title = "Kennzeichen: "..mods.plate,
description = GetRandomCarDescription(),
icon = "car",
onSelect = function()
print(CurrentZone.price)
if CurrentZone.price ~= nil then
lib.hideContext("StoredVehicles")
lib.registerContext({
id = "thisVehicle",
title = random.name,
options = {
{title = "Kosten: "..CurrentZone.price.."$"},
{title = "", disabled = true},
{
title = "Akzeptieren",
description = "Geld wird vom Bankkonto abgebucht!",
icon = "check",
onSelect = function()
lib.hideContext("thisVehicle")
QBCore.Functions.TriggerCallback('mh_garage:storedVehicle', function(cb)
if cb.status then
TriggerServerEvent('mh_Parking:deleteVehicle', mods.plate, NetworkGetNetworkIdFromEntity(vehicles[i]))
--DeleteVehicle(vehicles[i])
Notification(cb.text, cb.type, CurrentZone.name)
else
if cb.police and Config.EnabledPolice then
exports["roadphone"]:sendDispatch("Hier hat grade jemand versucht, ein Fahrzeug einzuparken.\nDas Zündschloss sah Beschädigt aus.\nKennzeichen: "..veh.plate, 'police', nil)
end
Notification(cb.text, cb.type)
end
end, mods, CurrentZone)
end
},
{
title = "Abbrechen",
description = "Das ist sehr Schade,",
icon = "close",
onSelect = function()
lib.hideContext("thisVehicle")
end
}
}
})
lib.showContext("thisVehicle")
else
QBCore.Functions.TriggerCallback('mh_garage:storedVehicle', function(cb)
if cb.status then
TriggerServerEvent('mh_Parking:deleteVehicle', mods.plate, NetworkGetNetworkIdFromEntity(vehicles[i]))
--DeleteVehicle(vehicles[i])
Notification(cb.text, cb.type, CurrentZone.name)
else
if cb.police and Config.EnabledPolice then
exports["roadphone"]:sendDispatch("Hier hat grade jemand versucht, ein Fahrzeug einzuparken.\nDas Zündschloss sah Beschädigt aus.\nKennzeichen: "..veh.plate, 'police', nil)
end
Notification(cb.text, cb.type)
end
end, mods, CurrentZone)
end
end
})
lib.registerContext({
id = "StoredVehicles",
title = random.name.."\n\n"..random.description,
options = opt
})
lib.showContext("StoredVehicles")
end
end
end)
function SelectName()
local names = {
{name = "Garagen-Guru",
description = "passt auf, dass keiner falsch parkt, Schranken nicht durchdrehen und Autos nicht fliegen!"},
{name = "Torflüsterer",
description = "Redet Schranken gut zu wenn diese wieder nicht funktionieren."},
{name = "Parkplatz-Papst",
description = "segnet jedes Auto, das diese heilige Halle betritt. Kein Ölverlust ohne dein Amen!"},
{name = "Schranken-Schamane",
description = "Elektronik und Technik? Kein Problem! Ich kontrolliere den Strom der Schranken mit reiner Willenskraft und Kabelbinder!"},
{name = "Chef vom Schuppen",
description = "Wer falsch parkt, kriegt nicht den Abschleppdienst, sondern eine Ansprache. Kurz. Hart. Legendär!"}
}
return names[math.random(1, #names)]
end
function GetRandomCarDescription()
local descriptions = {
"Das Auto für alle, die beim Beschleunigen mehr Spaß haben als beim Ankommen.",
"Wenn du ein Auto suchst, das schneller fährt als dein WiFi, bist du hier richtig.",
"Nicht das schnellste Auto, aber hey, es hat vier Räder und eine Hupe!",
"Das einzig wahre Fahren keine GPS-Fehler, nur pure Straßenmagie.",
"Mit diesem Auto wirst du die Straße genauso lieben wie das Benzin in deinem Tank.",
"Fahr einfach, und schau, wie viele Leute sich fragen, ob du gerade von der Rennstrecke kommst.",
"Dieses Auto bringt mehr Nervenkitzel als ein Achterbahn-Abenteuer!",
"Dieses Fahrzeug hat mehr Charakter als deine letzte Beziehung.",
"Es ist nicht das teuerste Auto, aber es fährt immer noch besser als deine Laune nach dem Montagmorgen!",
"Mit diesem Wagen wirst du zur Legende oder zumindest zum König des Parkhauses.",
"Schneller als dein Chefs Auto, langsamer als dein Instagram-Feed.",
"Wenn der Sound des Motors dich mehr motiviert als dein Wecker am Morgen!",
"Sicheres Fahren ist wichtig. Aber Spaß haben das ist die wahre Kunst!",
"Nicht der neueste Sportwagen, aber dafür ein echter Klassiker auf der Straße.",
"Weniger PS als dein Laptop, aber hey, er fährt!"
}
-- Zufällige Beschreibung auswählen
local randomIndex = math.random(1, #descriptions)
-- Beschreibung zurückgeben
return descriptions[randomIndex]
end

View file

@ -0,0 +1,44 @@
-- vehicleadmin.lua - Füge diese Datei in deinen client-Ordner ein
-- Füge einen Menüpunkt für das Fahrzeugadmin-System zum NPC-Menü hinzu
-- Diese Funktion wird aufgerufen, wenn der NPC-Target erstellt wird
local function AddVehicleAdminOption()
-- Prüfe, ob der Spieler die Berechtigung hat
QBCore.Functions.TriggerCallback('vehicleadmin:getPlayerJob', function(jobData)
if jobData and jobData.hasPermission then
-- Füge den Menüpunkt zum NPC hinzu, wenn der Spieler berechtigt ist
exports['qb-target']:AddTargetEntity(npcHandle, {
options = {
{
type = "client",
event = "vehicleadmin:openMenu",
icon = "fas fa-car-mechanic",
label = "Fahrzeugverwaltung",
}
},
distance = 2.5
})
end
end)
end
-- Registriere einen Event-Handler, der nach dem Hinzufügen der Standard-Target-Optionen ausgeführt wird
RegisterNetEvent('mh_garage:targetOptionsAdded')
AddEventHandler('mh_garage:targetOptionsAdded', function()
AddVehicleAdminOption()
end)
-- Füge einen Hook in die bestehende AddTargetOptions-Funktion ein
local originalAddTargetOptions = AddTargetOptions
AddTargetOptions = function()
originalAddTargetOptions()
TriggerEvent('mh_garage:targetOptionsAdded')
end
-- Registriere den Befehl für das Fahrzeugadmin-System
RegisterCommand('vehicleadmin', function()
TriggerEvent('vehicleadmin:openMenu')
end, false)
-- Registriere die Tastenbelegung (optional)
RegisterKeyMapping('vehicleadmin', 'Öffne Fahrzeug Admin Menu', 'keyboard', '')

View file

@ -0,0 +1,56 @@
-- vehicleadmin_integration.lua
-- Diese Datei integriert das Fahrzeugadmin-System mit dem Garagensystem
-- Füge einen Menüpunkt für das Fahrzeugadmin-System zum NPC-Menü hinzu
local function AddVehicleAdminOption()
-- Prüfe, ob der Spieler die Berechtigung hat
QBCore.Functions.TriggerCallback('vehicleadmin:getPlayerJob', function(jobData)
if jobData and jobData.hasPermission then
-- Füge den Menüpunkt zum NPC hinzu, wenn der Spieler berechtigt ist
if npcHandle and DoesEntityExist(npcHandle) then
exports['qb-target']:AddTargetEntity(npcHandle, {
options = {
{
type = "client",
event = "vehicleadmin:openMenu",
icon = "fas fa-car-mechanic",
label = "Fahrzeugverwaltung",
}
},
distance = 2.5
})
end
end
end)
end
-- Überschreibe die AddTargetOptions-Funktion, um unsere Option hinzuzufügen
local originalAddTargetOptions = AddTargetOptions
if originalAddTargetOptions then
AddTargetOptions = function()
originalAddTargetOptions()
AddVehicleAdminOption()
end
end
-- Registriere einen Event-Handler für das Hinzufügen der Target-Optionen
RegisterNetEvent('mh_garage:targetOptionsAdded')
AddEventHandler('mh_garage:targetOptionsAdded', function()
AddVehicleAdminOption()
end)
-- Füge einen Event-Handler hinzu, der nach dem Spawnen des NPCs ausgeführt wird
RegisterNetEvent('mh_garage:npcSpawned')
AddEventHandler('mh_garage:npcSpawned', function()
Wait(500) -- Warte kurz, damit der NPC vollständig gespawnt ist
AddVehicleAdminOption()
end)
-- Füge einen Hook in die SpawnGuardNPC-Funktion ein, um unseren Event auszulösen
local originalSpawnGuardNPC = SpawnGuardNPC
if originalSpawnGuardNPC then
SpawnGuardNPC = function(npc)
originalSpawnGuardNPC(npc)
TriggerEvent('mh_garage:npcSpawned')
end
end

View file

@ -0,0 +1,76 @@
RegisterNetEvent('mh_garage:verwaltungVeh')
AddEventHandler('mh_garage:verwaltungVeh', function()
QBCore.TriggerCallback('mh_garage:verwaltung', function(cb)
Debug("Verwaltung CB: "..json.encode(cb))
if cb[1] ~= nil then
local opt = {}
for i = 1, #cb, 1 do
local isingarage = cb[i].current_in_garage
local des = ""
if isingarage then
des = "Bearbeite das Fahrzeug\nKennzeichen: "..cb[i].current_plate.."\nGarage: "..cb[i].current_garage
else
des = "Bearbeite das Fahrzeug\nKennzeichen: "..cb[i].current_plate.."\nLetzte bekannte Garage: "..cb[i].current_garage
end
table.insert(opt, {
title = cb[i].current_name,
description = des,
icon = "car",
onSelect = function()
OpenVerwaltung(cb[i])
end
})
end
else
lib.notify({
title = "Fahrzeug Verwaltung",
description = "Du bist nicht im Besitz eines Fahrzeuges!",
type = "inform"
})
end
end)
end)
function OpenVerwaltung(vehicleInfos)
Debug("OpenVerwaltung: "..json.encode(vehicleInfos))
local garages = {}
for k, v in pairs(Config.Zonen) do
table.insert(garages, v.name)
end
lib.registerMenu({
id = 'some_menu_id',
title = 'Menu title',
position = 'top-right',
onSideScroll = function(selected, scrollIndex, args)
print("Scroll: ", selected, scrollIndex, args)
end,
onSelected = function(selected, secondary, args)
if not secondary then
print("Normal button")
else
if args.isCheck then
print("Check button")
end
if args.isScroll then
print("Scroll button")
end
end
print(selected, secondary, json.encode(args, {indent=true}))
end,
options = {
{label = "Name ändern", icon = "paper"},
{label = 'Transport to:', icon = 'arrows-up-down-left-right', values=garages},
}
}, function(selected, scrollIndex, args)
print(selected, scrollIndex, args)
end)
RegisterCommand('testmenu', function()
lib.showMenu('some_menu_id')
end)
end

View file

@ -0,0 +1,163 @@
Config = {}
Config.Debug = true
Config.PriceModel = false
Config.CallKeyVehicles = true
Config.Verwaltung = {
garage = false,
garage_change = 1000, -- Kosten um das Fahrzeug in einer anderen Garage zu versetzen.
garage_time = 10, -- Zeit in Minuten bis das Fahrzeug in der neuen Garage ist!
repair = false,
repair_car = 600, -- Kosten wenn das Fahrzeug Repariert werden soll
repair_time = 10, --Zeit in Minuten bis das Fahrzeug Repariert ist!
tank = false,
tank_car = 300, -- Kosten wenn das Fahrzeug getankt werden soll
tank_time = 3, -- Zeit in Minuten bis das Fahrzeug voll getankt ist
}
Config.Zonen = {
{
name = "pillboxgarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(213.9830, -808.7876, 30.0149, 166.4462),
model = "s_m_m_security_01",
distance = 30.0
},
vehicle_spawn = {
vector4(216.0978, -804.5941, 29.7967, 61.7316),
vector4(216.4074, -801.9208, 29.7928, 63.4452),
vector4(217.3208, -799.2861, 29.7802, 67.0612),
vector4(218.2428, -796.9962, 29.7684, 86.0924)
}
},
{
name = "grovegarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(-74.8718, -1824.8325, 25.9420, 249.5276),
model = "G_F_Y_Families_01",
distance = 30.0
},
vehicle_spawn = {
vector4(-62.5727, -1840.5316, 25.6792, 321.5169),
vector4(-60.1089, -1842.8113, 25.5816, 323.8478),
vector4(-60.1089, -1842.8113, 25.5816, 323.8478),
vector4(-54.8264, -1847.2078, 25.3799, 317.6915)
}
},
{
name = "vespuccigarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(-1184.6665, -1509.8247, 3.6493, 312.6639),
model = "s_m_m_security_01",
distance = 30.0
},
vehicle_spawn = {
vector4(-1191.6841, -1504.4095, 3.3688, 303.5921),
vector4(-1194.9706, -1500.1947, 3.3634, 310.3644),
vector4(-1196.7946, -1496.9948, 3.3652, 303.5580),
vector4(-1198.0444, -1493.9160, 3.3705, 311.4306)
}
},
{
name = "vinewoodgarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(-340.6176, 266.3422, 84.6795, 22.1944),
model = "s_m_m_security_01",
distance = 30.0
},
vehicle_spawn = {
vector4(-349.4257, 272.3725, 83.1077, 277.4619),
vector4(-349.8885, 276.0246, 83.9945, 276.0025),
vector4(-349.4035, 279.3595, 83.9495, 275.8749),
vector4(-349.3278, 282.7364, 83.9431, 278.1651)
}
},
{
name = "sandygarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(1637.97, 3796.94, 34.21, 189.13),
model = "s_m_m_security_01",
distance = 30.0
},
vehicle_spawn = {
vector4(1648.57, 3804.84, 33.81, 210.66),
vector4(1651.82, 3806.69, 33.82, 209.34),
vector4(1655.3, 3808.84, 33.83, 217.08),
vector4(1659.01, 3810.84, 33.84, 216.15)
}
},
{
name = "paletogarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(67.5230, 6412.9517, 30.4838, 230.1375),
model = "s_m_m_security_01",
distance = 30.0
},
vehicle_spawn = {
vector4(64.5739, 6406.8584, 30.2258, 208.3274),
vector4(61.9570, 6404.0493, 30.2258, 202.9158),
vector4(59.0419, 6401.0122, 30.2258, 211.3612),
vector4(73.0162, 6404.4741, 30.2258, 131.4993)
}
},
{
name = "eastsidegarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(1010.1598, -2289.5979, 29.5095, 186.2906),
model = "s_m_m_security_01",
distance = 30.0
},
vehicle_spawn = {
vector4(1003.6630, -2299.5208, 29.5095, 266.1712),
vector4(1005.3187, -2304.5198, 29.5095, 261.7787),
vector4(1005.2725, -2308.4385, 29.5095, 277.7379),
vector4(1004.2752, -2313.1870, 29.5095, 268.2005)
}
},
{
name = "cayogarage",
price = nil, --pro FZ
NPC = {
spawn = vector4(4505.5659, -4549.3335, 3.0960, 69.6222),
model = "IG_JuanStrickler",
distance = 30.0
},
vehicle_spawn = {
vector4(4501.0264, -4546.2852, 3.0278, 24.5729),
vector4(4497.0498, -4536.6616, 3.1636, 276.0185),
}
},
}
Config.Log = {
Webhook = "https://discord.com/api/webhooks/1366812966049288192/9ZjJC9_gLX6Fk-acf0YSW_haGWpCqG9zRGWaj0wCKLZefp8FX-GwNZBP77H6K93KfIw3",
Color = {
green = 56108,
grey = 8421504,
red = 16711680,
orange = 16744192,
blue = 2061822,
purple = 11750815,
},
SystemName = "Evolution_State_life Log [Garage]",
UserAvatar = '',
SystemAvatar = '',
}

View file

@ -0,0 +1,3 @@
cprice = {
["asea"] = 3,
}

View file

@ -0,0 +1,19 @@
author 'Mîhó'
fx_version 'adamant'
game 'gta5'
lua54 'yes'
client_scripts {
'@ox_lib/init.lua',
'config/*.lua',
'client/*.lua',
'client/vehicleadmin.lua'
}
server_scripts {
'@oxmysql/lib/MySQL.lua',
'config/*.lua',
'server/*.lua',
'server/vehicleadmin.lua'
}

View file

@ -0,0 +1,59 @@
function sendToDiscord(Titel, Text, Color) ------ Sende Nachricht an Channel: Join/Left
local embeds = {
{
["title"]=Text,
["type"]="rich",
["color"] =Config.Log.Color[Color],
["footer"]= {
["text"]= Config.Log.SystemName,
},
}
}
PerformHttpRequest(Config.Log.Webhook, function(err, text, headers)
if err and err ~= 204 and err ~= 200 then
print("Fehler beim Discord Webhook [" .. tostring(err) .. "]: " .. tostring(text))
end
end, 'POST', json.encode({ username = Titel.." - Mîhó",avatar_url = Config.SystemAvatar, embeds = embeds, tts = TTS}), { ['Content-Type'] = 'application/json' })
end
AddEventHandler('mh_garage:log')
RegisterServerEvent('mh_garage:log', function(type)
local Player = QBCore.Functions.GetPlayer(source)
local PlyData = Player.PlayerData
local Color = "purple"
Playerinfo = {
name = PlyData.charinfo.firstname .. " ".. PlyData.charinfo.lastname,
citizenid = PlyData.citizenid,
}
local user = Playerinfo.name.."[".. Playerinfo.citizenid .."]"
if type == "call" then
Text = "Dr. Teddy wurde von " .. user .." gerufen."
Color = "grey"
elseif type == "cash" then
Text = user.." hat Bar Bezahlt."
Color = "green"
elseif type == "bank" then
Text = user .. "hat via Überweisung Bezahlt."
Color = "green"
elseif type == "noMoney" then
Text = user .." Hatte nicht genug Geld."
Color = "orange"
elseif type == "noPlayer" then
Text = user .. " Wurde nicht gefunden und abgebrochen!"
Color = "red"
elseif type == "storno" then
Text = user .. " Hat die Behandlung doch Abgelehnt."
Color = "red"
elseif type == "heal" then
Text = user.. " Wurde Geheilt."
Color = "green"
elseif type == "end" then
Text = "NPC wurde wieder Entfernt..."
Color = "blue"
end
sendToDiscord("Garage", Text, Color)
end)

View file

@ -0,0 +1,159 @@
QBCore = exports['qb-core']:GetCoreObject()
local test_vari = {}
RegisterServerEvent('mh_garage:setMods')
AddEventHandler('mh_garage:setMods', function(mods)
if test_vari[mods.plate] == true then
return
else
MySQL.query("SELECT mods FROM player_vehicles WHERE plate = ?", {mods.plate}, function(rs)
-- Prüfen ob rs überhaupt Daten enthält
if rs and rs[1] then
-- Wenn mods ein String ist, konvertieren wir es zu einem Table
local modsData = type(rs[1].mods) == "string" and json.decode(rs[1].mods) or rs[1].mods
if not modsData or next(modsData) == nil then
-- Hier sollten Sie wahrscheinlich auch die mods-Daten übergeben, nicht nur plate
MySQL.query("UPDATE player_vehicles SET mods = ? WHERE plate = ?", {json.encode(mods), mods.plate})
test_vari[mods.plate] = true
else
test_vari[mods.plate] = true
end
end
end)
end
end)
QBCore.Functions.CreateCallback('mh_garage:storedVehicle', function(source, cb, veh, zone)
local Player = QBCore.Functions.GetPlayer(source)
if Player ~= nil then
if Player.Functions.GetMoney('bank', zone.price) then
MySQL.query("SELECT * FROM player_vehicles WHERE citizenid = ? AND plate = ?", {Player.PlayerData.citizenid, veh.plate}, function(rs)
if rs[1] ~= nil then
MySQL.query("UPDATE player_vehicles SET garage = ?, mods = ? WHERE plate = ?", {zone.name, json.encode(veh), veh.plate})
DelVehParking(veh.plate)
cb({
status = true,
text = "Fahrzeug erfolgreich eingeparkt!",
type = "success",
other = false,
police = false
})
else
MySQL.query("SELECT * FROM vehicle_keys WHERE owner = ? AND plate = ?", {Player.PlayerData.citizenid, veh.plate}, function(rs)
if rs[1] ~= nil then
MySQL.query("UPDATE player_vehicles SET garage = ?, mods = ? WHERE plate = ?", {zone.name, json.encode(veh), veh.plate})
--TriggerEvent('mh_Parking:removeVehicle', source, veh.plate)
--DelVehParking(veh.plate)
cb({
status = true,
text = "Fahrzeug erfolgreich eingeparkt!",
type = "success",
other = true,
police = false
})
else
cb({
status = false,
text = "Du besitzt kein Schlüssel für dieses Fahrzeug.",
type = "warning",
other = false,
police = true
})
end
end)
end
end)
end
end
end)
function DelVehParking(plate)
MySQL.query("SELECT * FROM vehicle_parking WHERE plate = ?", {plate}, function(rs)
if rs[1] ~= nil then
MySQL.query("DELETE FROM vehicle_parking WHERE plate = ?", {plate})
end
end)
end
QBCore.Functions.CreateCallback('mh_garage:retrieveOwnerVehicle', function(source, cb, zone)
local _source = source
local Player = QBCore.Functions.GetPlayer(_source)
local pedid = Player.PlayerData.citizenid
local veh = {}
MySQL.query("SELECT * FROM player_vehicles WHERE citizenid = ? and garage = ?", {pedid, zone}, function(rs)
if rs ~= nil and rs[1] ~= nil then
for k, v in pairs (rs) do
table.insert(veh, {
vehicle = v.vehicle,
mods = v.mods,
plate = v.plate,
name = v.name
})
end
Wait(100)
cb(veh)
else
cb(false)
end
end)
end)
QBCore.Functions.CreateCallback('mh_garage:retrieveKeyVehicle', function(source, cb, zone)
local _source = source
local Player = QBCore.Functions.GetPlayer(_source)
local pedid = Player.PlayerData.citizenid
local veh = {}
MySQL.query("SELECT pv.* FROM player_vehicles pv JOIN vehicle_keys vk ON pv.plate = vk.plate WHERE vk.owner = ? AND vk.count > 0 AND pv.garage = ?", {pedid, zone}, function(vehicles)
if vehicles and #vehicles > 0 then
for _, vehicle in pairs(vehicles) do
table.insert(veh, {
vehicle = vehicle.vehicle,
mods = vehicle.mods,
plate = vehicle.plate,
name = vehicle.name
})
end
Wait(100)
cb(veh)
else
cb(false)
end
end)
end)
QBCore.Functions.CreateCallback('mh_garage:verwaltung', function(source, cb)
local Player = QBCore.Functions.GetPlayer(source)
local vehicles = {}
MySQL.query("SELECT * FROM player_vehicles WHERE citizenid = ?", {Player.PlayerData.citizenid}, function(rs)
if rs and rs[1] then
for k, v in pairs(rs) do
table.insert(vehicles, {
current_garage = v.garage,
current_in_garage = v.parking,
current_name = v.name,
current_plate = v.plate,
})
end
end
end)
return vehicles
end)
RegisterServerEvent('mh_garage:spawnedVehicle')
AddEventHandler('mh_garage:spawnedVehicle', function(netID, plate)
MySQL.query("UPDATE player_vehicles SET garage = ? WHERE plate = ?", {"OUT", plate})
end)
QBCore.Functions.CreateCallback('mh_garage:CheckownerVehicles', function(source, cb, plate)
MySQL.query("SELECT * FROM player_vehicles WHERE plate = ?", {plate}, function(rs)
if rs ~= nil and rs[1] ~= nil then
cb(true)
else
cb(false)
end
end)
end)

View file

@ -0,0 +1,181 @@
-- vehicleadmin.lua - Füge diese Datei in deinen server-Ordner ein
-- Erweitere die Log-Funktion für das Fahrzeugadmin-System
RegisterServerEvent('vehicleadmin:log')
AddEventHandler('vehicleadmin:log', function(action, plate, garage)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
local playerName = Player.PlayerData.charinfo.firstname .. " " .. Player.PlayerData.charinfo.lastname
local citizenid = Player.PlayerData.citizenid
local jobName = Player.PlayerData.job.name
local jobLabel = Player.PlayerData.job.label
local logText = ""
local logColor = "blue"
if action == "move" then
logText = playerName .. " (" .. jobLabel .. ") hat Fahrzeug " .. plate .. " in Garage " .. garage .. " gestellt."
elseif action == "delete" then
logText = playerName .. " (" .. jobLabel .. ") hat Fahrzeug " .. plate .. " von der Map gelöscht."
logColor = "orange"
elseif action == "repair" then
logText = playerName .. " (" .. jobLabel .. ") hat Fahrzeug " .. plate .. " repariert."
logColor = "green"
end
-- Verwende die bestehende Log-Funktion
sendToDiscord("Fahrzeugadmin", logText, logColor)
-- Speichere den Log in der Datenbank (optional)
MySQL.Async.execute('INSERT INTO admin_logs (action, player_name, citizenid, job, target_plate, target_garage, timestamp) VALUES (?, ?, ?, ?, ?, ?, NOW())',
{action, playerName, citizenid, jobName, plate, garage or "none"})
end)
-- Füge zusätzliche Callback-Funktionen für erweiterte Features hinzu
QBCore.Functions.CreateCallback('vehicleadmin:getVehicleHistory', function(source, cb, plate)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then
cb(false)
return
end
-- Prüfe, ob der Spieler die Berechtigung hat
local hasPermission = false
local playerJob = Player.PlayerData.job.name
if QBCore.Functions.HasPermission(src, 'admin') then
hasPermission = true
elseif playerJob == 'police' or playerJob == 'mechanic' then
hasPermission = true
end
if not hasPermission then
cb(false)
return
end
-- Hole den Fahrzeugverlauf aus der Datenbank
MySQL.Async.fetchAll('SELECT * FROM admin_logs WHERE target_plate = ? ORDER BY timestamp DESC LIMIT 20', {plate}, function(results)
if results and #results > 0 then
cb(results)
else
cb({})
end
end)
end)
-- Füge einen Event-Handler für das Löschen von Fahrzeugen hinzu
RegisterServerEvent('vehicleadmin:deleteVehicle')
AddEventHandler('vehicleadmin:deleteVehicle', function(plate)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Prüfe, ob der Spieler die Berechtigung hat
local hasPermission = false
local playerJob = Player.PlayerData.job.name
if QBCore.Functions.HasPermission(src, 'admin') then
hasPermission = true
elseif playerJob == 'police' or playerJob == 'mechanic' then
hasPermission = true
end
if not hasPermission then
TriggerClientEvent('ox_lib:notify', src, {
title = 'Keine Berechtigung',
description = 'Du hast keine Berechtigung für diese Aktion',
type = 'error'
})
return
end
-- Lösche das Fahrzeug aus der Datenbank
MySQL.Async.execute('DELETE FROM player_vehicles WHERE plate = ?', {plate}, function(rowsChanged)
if rowsChanged > 0 then
-- Lösche auch alle zugehörigen Schlüssel
MySQL.Async.execute('DELETE FROM vehicle_keys WHERE plate = ?', {plate})
-- Lösche das Fahrzeug aus dem Parking-System
MySQL.Async.execute('DELETE FROM vehicle_parking WHERE plate = ?', {plate})
-- Log die Aktion
TriggerEvent('vehicleadmin:log', "delete_permanent", plate, nil)
TriggerClientEvent('ox_lib:notify', src, {
title = 'Fahrzeugverwaltung',
description = 'Fahrzeug wurde permanent gelöscht',
type = 'success'
})
else
TriggerClientEvent('ox_lib:notify', src, {
title = 'Fahrzeugverwaltung',
description = 'Fahrzeug nicht gefunden',
type = 'error'
})
end
end)
end)
-- Füge einen Event-Handler für das Ändern des Fahrzeugbesitzers hinzu
RegisterServerEvent('vehicleadmin:changeOwner')
AddEventHandler('vehicleadmin:changeOwner', function(plate, newOwnerCID)
local src = source
local Player = QBCore.Functions.GetPlayer(src)
if not Player then return end
-- Prüfe, ob der Spieler die Berechtigung hat
if not QBCore.Functions.HasPermission(src, 'admin') then
TriggerClientEvent('ox_lib:notify', src, {
title = 'Keine Berechtigung',
description = 'Nur Admins können den Besitzer ändern',
type = 'error'
})
return
end
-- Prüfe, ob der neue Besitzer existiert
MySQL.Async.fetchAll('SELECT * FROM players WHERE citizenid = ?', {newOwnerCID}, function(players)
if not players or #players == 0 then
TriggerClientEvent('ox_lib:notify', src, {
title = 'Fehler',
description = 'Spieler mit dieser Citizen ID nicht gefunden',
type = 'error'
})
return
end
-- Ändere den Besitzer des Fahrzeugs
MySQL.Async.execute('UPDATE player_vehicles SET citizenid = ? WHERE plate = ?', {newOwnerCID, plate}, function(rowsChanged)
if rowsChanged > 0 then
-- Erstelle einen neuen Schlüssel für den neuen Besitzer
MySQL.Async.execute('INSERT INTO vehicle_keys (plate, owner, count) VALUES (?, ?, 1) ON DUPLICATE KEY UPDATE count = 1', {plate, newOwnerCID})
-- Log die Aktion
local newOwnerInfo = json.decode(players[1].charinfo)
local newOwnerName = newOwnerInfo.firstname .. " " .. newOwnerInfo.lastname
TriggerEvent('vehicleadmin:log', "change_owner", plate, newOwnerName)
TriggerClientEvent('ox_lib:notify', src, {
title = 'Fahrzeugverwaltung',
description = 'Fahrzeugbesitzer wurde geändert',
type = 'success'
})
else
TriggerClientEvent('ox_lib:notify', src, {
title = 'Fahrzeugverwaltung',
description = 'Fahrzeug nicht gefunden',
type = 'error'
})
end
end)
end)
end)

View file

@ -0,0 +1,954 @@
-- error when both menus are enabled
if (Config.useContextMenu and Config.useNativeUI) then
print("^1[ERROR] You can use only one menu or no menu at all. You cannot use both menus."
.. "\nMake sure to set at least one to false in the config and to edit the fxmanifest accordingly!")
end
local keymakers = {}
local lockNotif = nil
local createNewKeyNotif = nil
local LockStatus = {
Unlocked = 1,
Locked = 2
}
local CB = exports["kimi_callbacks"]
local menuPoolNativeUI
local keymakerMenuNativeUI
local keyInvMenuNativeUI
if (Config.useNativeUI) then
if (NativeUI == nil) then
print("^1[ERROR] NativeUI was not properly initialized! Make sure to install NativeUI and start it before this resource!")
end
menuPoolNativeUI = NativeUI.CreatePool()
keymakerMenuNativeUI = NativeUI.CreateMenu(Config.Strings.keymakerTitle, Config.Strings.keymakerSub)
keyInvMenuNativeUI = NativeUI.CreateMenu(Config.Strings.keyInvTitle, Config.Strings.keyInvSub)
end
local isAtKeymaker = false
local menuOpen = false
-- create client side peds
Citizen.CreateThread(function()
for i, keymaker in ipairs(Config.Keymakers) do
RequestModel(keymaker.model)
while not HasModelLoaded(keymaker.model) do
Citizen.Wait(0)
end
local ped = CreatePed(0, keymaker.model, keymaker.pos.x, keymaker.pos.y, keymaker.pos.z, keymaker.pos.w, false, true)
SetBlockingOfNonTemporaryEvents(ped, true)
FreezeEntityPosition(ped, true)
SetEntityInvincible(ped, true)
table.insert(keymakers, ped)
-- add blip
local blip = AddBlipForCoord(keymaker.pos.x, keymaker.pos.y, keymaker.pos.z)
SetBlipSprite(blip, 134)
SetBlipColour(blip, 0)
SetBlipScale(blip, 1.0)
SetBlipDisplay(blip, 2)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName('STRING')
AddTextComponentSubstringPlayerName(Config.Strings.keymaker)
EndTextCommandSetBlipName(blip)
end
end)
-- main loop
Citizen.CreateThread(function()
while (true) do
Citizen.Wait(0)
if (Config.useNativeUI) then
if (menuOpen) then
menuPoolNativeUI:ProcessMenus()
end
if (isAtKeymaker) then
if (not menuOpen) then
ShowHelpText(Config.Strings.helpText)
end
if (IsControlJustPressed(0, 51)) then
if (menuPoolNativeUI:IsAnyMenuOpen()) then
menuPoolNativeUI:CloseAllMenus()
menuOpen = false
else
GenerateKeymakerMenuNativeUI()
keymakerMenuNativeUI:Visible(true)
menuOpen = true
end
end
end
if (IsControlJustPressed(0, Config.keyMenuKey)) then
if (menuPoolNativeUI:IsAnyMenuOpen()) then
menuPoolNativeUI:CloseAllMenus()
menuOpen = false
else
GenerateKeyInventoryNativeUI()
keyInvMenuNativeUI:Visible(true)
menuOpen = true
end
end
end
-- lock vehicle with key
--[[ if (Config.lockKey and IsControlJustPressed(0, Config.lockKey)) then
local vehicle = GetClosestVehicle(GetEntityCoords(PlayerPedId()), 10.0)
if (DoesEntityExist(vehicle) and IsVehicleOrKeyOwner(vehicle)) then
ToggleLock(vehicle, GetVehicleDoorLockStatus(vehicle) ~= LockStatus.Locked)
end
end ]]
end
end)
-- ContextMenu
if (Config.useContextMenu) then
local menuPool = MenuPool()
menuPool.OnOpenMenu = function(screenPosition, hitSomething, worldPosition, hitEntity, normalDirection)
local playerPos = GetEntityCoords(PlayerPedId())
if (Vdist(worldPosition.x, worldPosition.y, worldPosition.z, playerPos.x, playerPos.y, playerPos.z) < Config.maxDistance) then
CreateMenu(screenPosition, hitEntity)
end
end
function CreateMenu(screenPosition, hitEntity)
menuPool:Reset()
if (hitEntity) then
if (hitEntity == PlayerPedId()) then
local vehicleData = CB:Trigger("VKC:getPlayerVehicleData")
local ownedKeys = GetPlayerKeys()
menuPool:Reset()
local playerMenu = menuPool:AddMenu()
local ownVehMenu, ownVehMenuItem = menuPool:AddSubmenu(playerMenu, Config.Strings.CM.masterKeysTitle)
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
local item = ownVehMenu:AddItem(model .. " " .. plate)
end
local ownKeyMenu, ownKeyMenuItem = menuPool:AddSubmenu(playerMenu, Config.Strings.CM.keysTitle)
for i, key in ipairs(ownedKeys) do
local model = GetLabelText(GetDisplayNameFromVehicleModel(key.model))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(key.model)
end
local item = ownKeyMenu:AddItem(model .. " " .. key.plate)
item.rightText = Text("x" .. key.count)
end
-- Fahrzeugübergabe-Menü hinzufügen
local transferVehMenu, transferVehMenuItem = menuPool:AddSubmenu(playerMenu, "Fahrzeug übergeben")
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
local item = transferVehMenu:AddItem(model .. " " .. plate)
item.closeMenuOnClick = true
item.OnClick = function()
-- Finde den nächsten Spieler
local player = GetClosestPlayer(5.0)
if player then
local targetPlayerId = GetPlayerServerId(player)
local playerName = GetPlayerName(player)
local success = CB:Trigger("VKC:transferVehicleOwnership", plate, targetPlayerId)
if success then
Notification(string.format("Du hast dein %s an %s übergeben", model, playerName))
else
Notification("Übergabe fehlgeschlagen")
end
else
Notification("Kein Spieler in der Nähe gefunden")
end
end
end
playerMenu:SetPosition(screenPosition)
playerMenu:Visible(true)
elseif (IsEntityAKeymaker(hitEntity)) then
local vehicleData = GetPlayerVehicleData()
local ownedKeys = GetPlayerKeys()
menuPool:Reset()
local keymakerMenu = menuPool:AddMenu()
local createKeyMenu, createKeyMenuItem = menuPool:AddSubmenu(keymakerMenu, Config.Strings.CM.createKeyTitle)
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
local keyCount = GetKeyCount(plate, ownedKeys)
local keyItem = createKeyMenu:AddItem(model .. " " .. plate)
keyItem.rightText = Text("x" .. tostring(keyCount))
keyItem.OnClick = function()
local result = CB:Trigger("VKC:createNewKey", plate, 1)
if (type(result) == "boolean" and result == true) then
Notification(string.format(Config.Strings.createSuccess, model))
local oldCount = keyItem.rightText.title:gsub("x", "")
keyItem.rightText.title = "x" .. tostring(tonumber(oldCount) + 1)
elseif (type(result) == "string" and result == "noMoney") then
Notification(string.format(Config.Strings.createNoMoney, model))
else
Notification(string.format(Config.Strings.createFailed, model))
end
end
end
local invalidateKeyMenu, invalidateKeyMenuItem = menuPool:AddSubmenu(keymakerMenu, Config.Strings.CM.invalKeyTitle)
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
local item = invalidateKeyMenu:AddItem(model .. " " .. plate)
item.closeMenuOnClick = true
item.OnClick = function()
local result = CB:Trigger("VKC:removeAllKeys", plate)
if (type(result) == "boolean" and result == true) then
Notification(string.format(Config.Strings.deleteKeys, model))
elseif (type(result) == "string" and result == "noMoney") then
Notification(string.format(Config.Strings.removeNoMoney, vehName))
else
Notification(string.format(Config.Strings.removeFailed, vehName))
end
end
end
keymakerMenu:SetPosition(screenPosition)
keymakerMenu:Visible(true)
elseif (IsEntityAPed(hitEntity) and IsPedAPlayer(hitEntity)) then
local ownedKeys = GetPlayerKeys()
menuPool:Reset()
local interactMenu = menuPool:AddMenu()
local giveKeyMenu, giveKeyMenuItem = menuPool:AddSubmenu(interactMenu, Config.Strings.CM.giveKey)
for i, key in ipairs(ownedKeys) do
local model = GetLabelText(GetDisplayNameFromVehicleModel(key.model))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(key.model)
end
local keyItem = giveKeyMenu:AddItem(model .. " " .. key.plate)
keyItem.rightText = Text("x" .. key.count)
keyItem.closeMenuOnClick = true
keyItem.OnClick = function()
local players = GetActivePlayers()
for i, player in ipairs(players) do
if (GetPlayerPed(player) == hitEntity) then
local success = CB:Trigger("VKC:giveKeyToPlayer", key.plate, GetPlayerServerId(player))
if (success) then
Notification(string.format(Config.Strings.giveSuccess, model))
else
Notification(string.format(Config.Strings.giveFailed, model))
end
break
end
end
end
end
interactMenu:SetPosition(screenPosition)
interactMenu:Visible(true)
end
end
end
function IsEntityAKeymaker(entity)
for i, keymaker in ipairs(keymakers) do
if (entity == keymaker) then
return true
end
end
return false
end
end
-- NativeUI
function GenerateKeymakerMenuNativeUI()
keymakerMenuNativeUI:Clear()
local vehicleData = GetPlayerVehicleData()
local ownedKeys = GetPlayerKeys()
keymakerMenuNativeUI = NativeUI.CreateMenu(Config.Strings.NUI.keymakerMenuTitle, Config.Strings.NUI.keymakerMenuSub)
menuPoolNativeUI:Add(keymakerMenuNativeUI)
local submenuCreateKey = menuPoolNativeUI:AddSubMenu(keymakerMenuNativeUI, Config.Strings.NUI.createKeyTitle, Config.Strings.NUI.createKeyDesc)
submenuCreateKey.ParentItem:RightLabel(">")
submenuCreateKey.Subtitle.Text._Text = "~b~" .. Config.Strings.NUI.createKeyTitle
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
local keyCount = GetKeyCount(plate, ownedKeys)
local vehItem = NativeUI.CreateItem(model .. " " .. plate, string.format(Config.Strings.NUI.createVehicleKey, model, plate))
vehItem:RightLabel("x" .. tostring(keyCount))
submenuCreateKey:AddItem(vehItem)
end
submenuCreateKey.OnItemSelect = function(menu, item, index)
Citizen.CreateThread(function()
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicleData[index][2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicleData[index][2])
end
local result = CB:Trigger("VKC:createNewKey", vehicleData[index][1], 1)
if (type(result) == "boolean" and result == true) then
Notification(string.format(Config.Strings.createSuccess, model))
if (submenuCreateKey:Visible()) then
local oldCount = item.Label.Text._Text:gsub("x", "")
item:RightLabel("x" .. tostring(tonumber(oldCount) + 1))
end
elseif (type(result) == "string" and result == "noMoney") then
Notification(string.format(Config.Strings.createNoMoney, model))
else
Notification(string.format(Config.Strings.createFailed, model))
end
end)
end
local submenuInvalidateKey = menuPoolNativeUI:AddSubMenu(keymakerMenuNativeUI, Config.Strings.NUI.invalKeyTitle, Config.Strings.NUI.invalKeyDesc)
submenuInvalidateKey.ParentItem:RightLabel(">")
submenuInvalidateKey.Subtitle.Text._Text = "~b~" .. Config.Strings.NUI.invalKeyTitle
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
local keyItem = NativeUI.CreateItem(model .. " " .. plate, string.format(Config.Strings.NUI.invalVehicleKey, model, plate))
submenuInvalidateKey:AddItem(keyItem)
end
submenuInvalidateKey.OnItemSelect = function(menu, item, index)
Citizen.CreateThread(function()
local result = CB:Trigger("VKC:removeAllKeys", vehicleData[index][1])
if (type(result) == "boolean" and result == true) then
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicleData[index][2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicleData[index][2])
end
Notification(string.format(Config.Strings.deleteKeys, model))
elseif (type(result) == "string" and result == "noMoney") then
Notification(string.format(Config.Strings.removeNoMoney, vehName))
else
Notification(string.format(Config.Strings.removeFailed, vehName))
end
end)
end
keymakerMenuNativeUI.OnMenuClosed = function(menu)
menuOpen = false
end
menuPoolNativeUI:ControlDisablingEnabled(false)
menuPoolNativeUI:MouseControlsEnabled(false)
menuPoolNativeUI:RefreshIndex()
end
function GenerateKeyInventoryNativeUI()
keyInvMenuNativeUI:Clear()
local vehicleData = GetPlayerVehicleData()
local ownedKeys = GetPlayerKeys()
keyInvMenuNativeUI = NativeUI.CreateMenu(Config.Strings.NUI.keyInventoryTitle, Config.Strings.NUI.keyInventorySub)
menuPoolNativeUI:Add(keyInvMenuNativeUI)
local submenuShowMasterKeys = menuPoolNativeUI:AddSubMenu(keyInvMenuNativeUI, Config.Strings.NUI.masterKeysTitle, Config.Strings.NUI.masterKeysDesc)
submenuShowMasterKeys.ParentItem:RightLabel(">")
submenuShowMasterKeys.Subtitle.Text._Text = "~b~" .. Config.Strings.NUI.masterKeysTitle
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
--local keyCount = GetKeyCount(plate, ownedKeys)
local vehItem = NativeUI.CreateItem(model .. " " .. plate, "")
--vehItem:RightLabel("x" .. tostring(keyCount))
submenuShowMasterKeys:AddItem(vehItem)
end
-- Fahrzeugübergabe-Untermenü hinzufügen
local submenuTransferVehicle = menuPoolNativeUI:AddSubMenu(keyInvMenuNativeUI, "Fahrzeug übergeben", "Übertrage ein Fahrzeug an einen Spieler in der Nähe")
submenuTransferVehicle.ParentItem:RightLabel(">")
submenuTransferVehicle.Subtitle.Text._Text = "~b~Fahrzeug übergeben"
for i, vehicle in ipairs(vehicleData) do
local plate = vehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(vehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(vehicle[2])
end
local vehItem = NativeUI.CreateItem(model .. " " .. plate, "Wähle dieses Fahrzeug zum Übergeben")
submenuTransferVehicle:AddItem(vehItem)
end
submenuTransferVehicle.OnItemSelect = function(menu, item, index)
local selectedVehicle = vehicleData[index]
local plate = selectedVehicle[1]
local model = GetLabelText(GetDisplayNameFromVehicleModel(selectedVehicle[2]))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(selectedVehicle[2])
end
-- Finde den nächsten Spieler
local player = GetClosestPlayer(5.0)
if player then
local targetPlayerId = GetPlayerServerId(player)
local playerName = GetPlayerName(player)
local success = CB:Trigger("VKC:transferVehicleOwnership", plate, targetPlayerId)
if success then
lib.notify({
title = "Fahrzeug übergeben",
description = "Du hast dein " .. model .. " an " .. playerName .. " übergeben",
position = "top",
type = "success",
icon = "car"
})
else
lib.notify({
title = "Fahrzeug übergeben",
description = "Übergabe fehlgeschlagen",
position = "top",
type = "error",
icon = "car"
})
end
else
lib.notify({
title = "Fahrzeug übergeben",
description = "Kein Spieler in der Nähe gefunden",
position = "top",
type = "error",
icon = "car"
})
end
menuPoolNativeUI:CloseAllMenus()
menuOpen = false
end
local submenuShowKeys = menuPoolNativeUI:AddSubMenu(keyInvMenuNativeUI, Config.Strings.NUI.keysTitle, Config.Strings.NUI.keysDesc)
submenuShowKeys.ParentItem:RightLabel(">")
submenuShowKeys.Subtitle.Text._Text = "~b~" .. Config.Strings.NUI.keysTitle
for i, key in ipairs(ownedKeys) do
local model = GetLabelText(GetDisplayNameFromVehicleModel(key.model))
if (model == "NULL") then
model = GetDisplayNameFromVehicleModel(key.model)
end
local submenuKey = menuPoolNativeUI:AddSubMenu(submenuShowKeys, model .. " " .. key.plate, "")
submenuKey.ParentItem:RightLabel("x" .. tostring(key.count) .. " >")
submenuKey.Subtitle.Text._Text = "~b~" .. model .. " " .. key.plate
local giveItem = NativeUI.CreateItem(Config.Strings.NUI.giveKeyTitle, Config.Strings.NUI.giveKeyDesc)
submenuKey:AddItem(giveItem)
local removeItem = NativeUI.CreateItem(Config.Strings.NUI.removeKeyTitle, Config.Strings.NUI.removeKeyDesc)
submenuKey:AddItem(removeItem)
submenuKey.OnItemSelect = function(menu, item, index)
if (item == giveItem) then
Citizen.CreateThread(function()
local player = GetClosestPlayer(2.0)
if (player) then
local success = CB:Trigger("VKC:giveKeyToPlayer", key.plate, GetPlayerServerId(player))
if (success) then
Notification(string.format(Config.Strings.giveSuccess, key.plate))
else
Notification(string.format(Config.Strings.giveFailed, key.plate))
end
else
Notification(string.format(Config.Strings.giveFailed, key.plate))
end
end)
elseif (item == removeItem) then
Citizen.CreateThread(function()
local success = CB:Trigger("VKC:removeKey", key.plate, 1)
if (success) then
Notification(string.format(Config.Strings.removeSuccess, key.plate))
if (submenuShowKeys:Visible() or submenuKey:Visible()) then
local oldCount = submenuKey.ParentItem.Label.Text._Text:gsub("x", "")
oldCount = oldCount:gsub(" >", "")
submenuKey.ParentItem:RightLabel("x" .. tostring(tonumber(oldCount) - 1) .. " >")
end
else
Notification(string.format(Config.Strings.removeFailed, key.plate))
end
end)
end
end
end
keyInvMenuNativeUI.OnMenuClosed = function(menu)
menuOpen = false
end
menuPoolNativeUI:ControlDisablingEnabled(false)
menuPoolNativeUI:MouseControlsEnabled(false)
menuPoolNativeUI:RefreshIndex()
end
if (Config.useNativeUI) then
Citizen.CreateThread(function()
while (true) do
Citizen.Wait(250)
isAtKeymaker = false
local pos = GetEntityCoords(PlayerPedId())
for i, keymaker in ipairs(Config.Keymakers) do
if (Vdist(pos.x, pos.y, pos.z, keymaker.pos.x, keymaker.pos.y, keymaker.pos.z) < 2.0) then
isAtKeymaker = true
break
end
end
end
end)
if (Config.keyMenuCommand) then
RegisterCommand(Config.keyMenuCommand, function(source, args, raw)
if (menuPoolNativeUI:IsAnyMenuOpen()) then
menuPoolNativeUI:CloseAllMenus()
end
GenerateKeyInventoryNativeUI()
keyInvMenuNativeUI:Visible(true)
menuOpen = true
end, false)
end
end
-- lock vehicle
if (Config.lockCommand) then
RegisterCommand(Config.lockCommand, function(source, args, raw)
local vehicle = GetClosestVehicle(GetEntityCoords(PlayerPedId()), 10.0)
if (DoesEntityExist(vehicle) and IsVehicleOrKeyOwner(vehicle)) then
ToggleLock(vehicle, GetVehicleDoorLockStatus(vehicle) ~= LockStatus.Locked)
end
end, false)
end
function ToggleLock(vehicle, lock)
local lockStatus = GetVehicleDoorLockStatus(vehicle)
if (NetworkHasControlOfEntity(vehicle)) then
ToggleLockOnVehicle(vehicle, lock)
else
TriggerServerEvent("VKC:toggleLockNet", NetworkGetNetworkIdFromEntity(vehicle), lock)
end
-- play sound
TriggerServerEvent("VKC:playDoorLockSoundNet", NetworkGetNetworkIdFromEntity(vehicle), lock)
-- play remote animation
if (not IsPedInAnyVehicle(PlayerPedId(), false)) then
PlayRemoteAnimation()
end
-- show notification
--[[
if (lockNotif) then
RemoveNotification(lockNotif)
end
lockNotif = Notification(lockStatus == LockStatus.Locked and Config.Strings.unlockNotif or Config.Strings.lockNotif)
]]
end
function ToggleLockOnVehicle(vehicle, lock)
if (lock) then
SetVehicleDoorsShut(vehicle, false)
SetVehicleDoorsLocked(vehicle, LockStatus.Locked)
--exports['okokNotify']:Alert("Fahrzeug", "Du hast dein Fahrzeug Abgeschlossen", 3000, 'error')
lib.notify({
title = "Fahrzeug",
description = "Du hast dein Fahrzeug Abgeschlossen",
position = "top",
type = "error",
icon = "car"
})
SetVehicleLights(vehicle, 2)
Citizen.Wait(150)
SetVehicleLights(vehicle, 0)
Citizen.Wait(150)
SetVehicleLights(vehicle, 2)
Citizen.Wait(150)
SetVehicleLights(vehicle, 0)
else
SetVehicleDoorsLocked(vehicle, LockStatus.Unlocked)
--exports['okokNotify']:Alert("Fahrzeug", "Du hast dein Fahrzeug Aufgeschlossen", 3000, 'success')
lib.notify({
title = "Fahrzeug",
description = "Du hast dein Fahrzeug Aufgeschlossen",
position = "top",
type = "success",
icon = "car"
})
SetVehicleLights(vehicle, 2)
Citizen.Wait(150)
SetVehicleLights(vehicle, 0)
Citizen.Wait(150)
SetVehicleLights(vehicle, 2)
Citizen.Wait(150)
SetVehicleLights(vehicle, 0)
Citizen.Wait(150)
SetVehicleLights(vehicle, 2)
Citizen.Wait(150)
SetVehicleLights(vehicle, 0)
Citizen.Wait(150)
SetVehicleLights(vehicle, 2)
Citizen.Wait(150)
SetVehicleLights(vehicle, 0)
Citizen.Wait(150)
SetVehicleLights(vehicle, 2)
Citizen.Wait(150)
SetVehicleLights(vehicle, 0)
end
end
function PlayDoorLockSound(vehicle, lock)
if (lock) then
PlayVehicleDoorCloseSound(vehicle, 0)
else
PlayVehicleDoorOpenSound(vehicle, 0)
end
end
if (Config.lockCommand or Config.lockKey) then
RegisterNetEvent("VKC:toggleLockOnPlayer")
AddEventHandler("VKC:toggleLockOnPlayer", function(vehicleNetId, unlocked)
local vehicle = NetworkGetEntityFromNetworkId(vehicleNetId)
if (DoesEntityExist(vehicle) and NetworkHasControlOfEntity(vehicle)) then
local lockStatus = GetVehicleDoorLockStatus(vehicle)
ToggleLockOnVehicle(vehicle, unlocked)
end
end)
RegisterNetEvent("VKC:playDoorLockSound")
AddEventHandler("VKC:playDoorLockSound", function(vehicleNetId, lock)
local vehicle = NetworkGetEntityFromNetworkId(vehicleNetId)
if (DoesEntityExist(vehicle)) then
PlayDoorLockSound(vehicle, lock)
end
end)
end
RegisterNetEvent("VKC:giveKeyNotif")
AddEventHandler("VKC:giveKeyNotif", function(plate)
Notification(string.format(Config.Strings.giveSuccessPly, plate))
end)
-- Ereignis für Fahrzeugübergabe-Benachrichtigung
RegisterNetEvent("VKC:vehicleTransferNotif")
AddEventHandler("VKC:vehicleTransferNotif", function(plate, model)
lib.notify({
title = "Fahrzeug erhalten",
description = "Du hast ein " .. model .. " mit dem Kennzeichen " .. plate .. " erhalten",
position = "top",
type = "success",
icon = "car"
})
end)
function IsVehicleOwner(vehicle)
if (not DoesEntityExist(vehicle)) then
print("^1[ERROR] Parameter \"vehicle\" was nil or vehicle did not exist while triggering export \"IsVehicleOwner\"!")
return
end
return CB:Trigger("VKC:isVehicleOwner", GetVehicleNumberPlateText(vehicle))
end
function IsKeyOwner(vehicle)
if (not DoesEntityExist(vehicle)) then
print("^1[ERROR] Parameter \"vehicle\" was nil or vehicle did not exist while triggering export \"IsKeyOwner\"!")
return
end
return CB:Trigger("VKC:isKeyOwner", GetVehicleNumberPlateText(vehicle), GetEntityModel(vehicle))
end
function IsVehicleOrKeyOwner(vehicle)
if (not DoesEntityExist(vehicle)) then
print("^1[ERROR] Parameter \"vehicle\" was nil or vehicle did not exist while triggering export \"IsVehicleOrKeyOwner\"!")
return
end
return CB:Trigger("VKC:isVehicleOrKeyOwner", GetVehicleNumberPlateText(vehicle), GetEntityModel(vehicle))
end
function GetPlayerKeys()
return CB:Trigger("VKC:getPlayerKeys")
end
function GetPlayerVehicleData()
return CB:Trigger("VKC:getPlayerVehicleData")
end
-- play lock animation
function PlayRemoteAnimation()
Citizen.CreateThread(function()
RequestModel(keyPropHash)
RequestAnimDict("anim@mp_player_intmenu@key_fob@")
while (not HasModelLoaded(keyPropHash) and not HasAnimDictLoaded("anim@mp_player_intmenu@key_fob@")) do
Citizen.Wait(0)
end
local playerPed = PlayerPedId()
local playerPos = GetEntityCoords(playerPed)
local keyObj = CreateObjectNoOffset(keyPropHash, playerPos.x, playerPos.y, playerPos.z, true, true, false)
SetModelAsNoLongerNeeded(keyPropHash)
local boneIndex = GetEntityBoneIndexByName(playerPed, "IK_R_Hand")
local offset = vector3(0.08, 0.025, -0.01)
local rotOffset = vector3(0, 70, 140)
AttachEntityToEntity(keyObj, playerPed, boneIndex, offset.x, offset.y, offset.z, rotOffset.x, rotOffset.y, rotOffset.z, false, false, true, false, 2, true)
TaskPlayAnim(playerPed, "anim@mp_player_intmenu@key_fob@", "fob_click_fp", 8.0, 8.0, -1, 48, 1, false, false, false)
RemoveAnimDict("anim@mp_player_intmenu@key_fob@")
Citizen.Wait(1500)
DeleteEntity(keyObj)
end)
end
-- show text in upper left corner
function ShowHelpText(text)
BeginTextCommandDisplayHelp('STRING')
AddTextComponentSubstringPlayerName(text)
EndTextCommandDisplayHelp(0, false, true, -1)
end
-- displays a notification and returns its handle
function Notification(text)
SetNotificationTextEntry('STRING')
AddTextComponentSubstringPlayerName(text)
return DrawNotification(false, true)
end
-- get key count
function GetKeyCount(plate, keyArray)
for i, key in ipairs(keyArray) do
if (plate == key.plate) then
return key.count
end
end
return 0
end
-- get all players
function GetAllPlayers()
local players = {}
for k, v in ipairs(GetActivePlayers()) do
table.insert(players, v)
end
return players
end
-- get the closest player
function GetClosestPlayer(maxRange)
local playerPed = PlayerPedId()
local players = GetAllPlayers()
local playerCoords = GetEntityCoords(playerPed)
local closestDistance = maxRange
local closestPlayer = nil
for i=1, #players, 1 do
local coords = GetEntityCoords(GetPlayerPed(players[i]))
local dist = Vdist(playerCoords.x, playerCoords.y, playerCoords.z, coords.x, coords.y, coords.z)
if (dist < closestDistance and players[i] ~= PlayerId()) then
closestDistance = dist
closestPlayer = players[i]
end
end
if (closestPlayer ~= nil and DoesEntityExist(GetPlayerPed(closestPlayer))) then
return closestPlayer
else
return nil
end
end
-- Return closest loaded vehicle entity or nil if no vehicle is found
function GetClosestVehicle(position, maxRadius)
local vehicles = GetAllVehicles()
local dist = maxRadius
local closestVehicle = nil
for i=1, #vehicles, 1 do
local vehicleCoords = GetEntityCoords(vehicles[i])
local tempDist = Vdist(vehicleCoords.x, vehicleCoords.y, vehicleCoords.z, position.x, position.y, position.z)
if (tempDist < dist) then
dist = tempDist
closestVehicle = vehicles[i]
end
end
if (closestVehicle ~= nil and DoesEntityExist(closestVehicle)) then
return closestVehicle
else
return nil
end
end
-- Returns all loaded vehicles on client side
function GetAllVehicles()
local vehicles = {}
for vehicle in EnumerateVehicles() do
table.insert(vehicles, vehicle)
end
return vehicles
end
-- getting all vehicles
function EnumerateVehicles()
return EnumerateEntities(FindFirstVehicle, FindNextVehicle, EndFindVehicle)
end
function EnumerateEntities(initFunc, moveFunc, disposeFunc)
return coroutine.wrap(function()
local iter, id = initFunc()
if not id or id == 0 then
disposeFunc(iter)
return
end
local enum = {handle = iter, destructor = disposeFunc}
setmetatable(enum, entityEnumerator)
local next = true
repeat
coroutine.yield(id)
next, id = moveFunc(iter)
until not next
enum.destructor, enum.handle = nil, nil
disposeFunc(iter)
end)
end
local entityEnumerator = {
__gc = function(enum)
if enum.destructor and enum.handle then
enum.destructor(enum.handle)
end
enum.destructor = nil
enum.handle = nil
end
}
--[[
RegisterCommand("plate", function(source, args, raw)
local vehicle = GetClosestVehicle(GetEntityCoords(PlayerPedId()), 10.0)
if (DoesEntityExist(vehicle)) then
SetVehicleNumberPlateText(vehicle, args[1])
end
end, false)
]]
RegisterKeyMapping("vehicleLock", "Vehicle Lock", "keyboard", "PAGEUP")
RegisterCommand("vehicleLock", function()
local vehicle = GetClosestVehicle(GetEntityCoords(PlayerPedId()), 10.0)
if (DoesEntityExist(vehicle) and IsVehicleOrKeyOwner(vehicle)) then
ToggleLock(vehicle, GetVehicleDoorLockStatus(vehicle) ~= LockStatus.Locked)
end
end, false)
-- Integration mit dem Mietfahrzeug-System
RegisterNetEvent('vehiclerental:client:checkRentalKeys')
AddEventHandler('vehiclerental:client:checkRentalKeys', function()
TriggerServerEvent('vehiclerental:server:checkRentalKeys')
end)
-- Überprüfe Mietfahrzeug-Schlüssel beim Öffnen des Schlüssel-Menüs
if Config.useNativeUI then
-- Speichere die ursprüngliche Funktion
local originalGenerateKeyInventoryNativeUI = GenerateKeyInventoryNativeUI
-- Überschreibe die Funktion
GenerateKeyInventoryNativeUI = function()
-- Überprüfe Mietfahrzeug-Schlüssel
TriggerServerEvent('vehiclerental:server:checkRentalKeys')
-- Warte kurz, damit die Schlüssel aktualisiert werden können
Citizen.Wait(100)
-- Rufe die ursprüngliche Funktion auf
originalGenerateKeyInventoryNativeUI()
end
end

View file

@ -0,0 +1,149 @@
Config = {}
-- set at least one to false, otherwise both menus will clash
-- and don't forget to comment the menu in the fxmanifest.lua if you don't use it!
-- Setting both to true does not work!
-- setting both to false results in no menu being usable at all
-- you can then just include the exports in your own menu if you have one
Config.useContextMenu = false
Config.useNativeUI = true
-- set to nil if you don't want to use the command
Config.lockCommand = "lock"
-- set to nil if you don't want to use the button (303, U)
Config.lockKey = 10 -- PageUp
-- set to nil if you don't want to use the command
Config.keyMenuCommand = "keys"
-- set to nil if you don't want to use the button (311, K)
Config.keyMenuKey = 56 -- F9
-- maximum distance (in meters) between the player and ContextMenu interaction point
Config.maxDistance = 5.0
-- you can define more keymakers by just adding additional entries
Config.Keymakers = {
{
-- ped model (yes, the ` symbols are correct)
model = `s_m_m_autoshop_01`,
-- position and heading vector4(x, y, z, heading)
pos = vector4(170.33, -1799.13, 28.32, 313.0)
},
}
-- define costs for creating a new key and exchanging locks
Config.Costs = {
GetMoney = "cash", -- cash or bank
newKey = 150,
exchangeLocks = 650
}
-- define job vehicles like this (this acts as a key for those vehicles)
-- models in ``
-- either exact plates or just a string that should be in the vehicles plate
-- e.g. "LSPD" will let a police officer lock/unlock any vehicle with the plate "LSPD1337" or "13LSPD37"
Config.JobVehicles = {
-- examples:
--["police"] = {
--models = {
--`police`,
--`policeb`,
--`pbus`
--},
--plates = {
--""
--}
--},
--["mechanic"] = {
-- models = {
-- `flatbed`
-- },
-- plates = {
-- "MECH"
-- }
--},
}
Config.Strings = {
keymaker = "Schlüsseldienst",
helpText = "~w~Drück ~g~E ~w~um mit den Schlüsseldienst zu reden",
lockNotif = "~r~Fahrzeug Abgeschlossen",
unlockNotif = "~g~Fahrzeug Aufgeschlossen",
createSuccess = "~g~Ersatzschlüssel Nachgemacht %s.",
createNoMoney = "~r~Schlüssel kann nicht nachgemacht werden, du hast zu wenig Geld dabei",
createFailed = "~r~Ersatzschlüssel machen Fehlgeschlagen %s!",
giveSuccess = "~g~Schlüssel an %s Weitergegeben.",
giveSuccessPly = "~g~Schlüssel von %s erhalten ",
giveFailed = "~r~Schlüssel geben von %s Fehlgeschlagen",
removeSuccess = "~r~Schlüssel für ~g~%s ~r~aus deiner Tasche Entfernt",
removeNoMoney = "~r~Schloss kann nicht bezahlt werden, du hast kein Geld dabei",
removeFailed = "~r~Entfernen von Schlüssel für ~g~%s ~r~Fehgeschlagen!",
deleteKeys = "~r~Du hast für dein ~g~%s ~r~Das Schloss ausgetasucht, und somit sind jetzt alle Schlüssel nicht mehr Gültig.",
-- NativeUI
NUI = {
-- keymaker menu
keymakerMenuTitle = "Schlüssel-System",
keymakerMenuSub = "~g~Erstelle Schlüssel und Tausche deine Fahrzeug Schlösser aus",
-- create key
createKeyTitle = "~g~Ersatzschlüssel erstellen",
createKeyDesc = "Erstelle einen neuen Schlüssel für eines Ihrer Fahrzeuge ",
createVehicleKey = "Erstelle einen Schlüssel für Ihr %s mit den Kennzeichen %s. ~g~Preis "..Config.Costs.newKey.."$",
-- invalidate Key
invalKeyTitle = "~r~Fahrzeug Schlöss Tauschen",
invalKeyDesc = "Tausche die Schlösser von Ihren Auto aus, alle schlüssel sind damit nicht mehr Gültig!",
invalVehicleKey = "Schloss Tauschen von %s mit den Kennzeichen %s. ~g~Preis "..Config.Costs.exchangeLocks.."$",
-- key Inventory
keyInventoryTitle = "Schlüssel",
keyInventorySub = "~g~Da sind alle deine Schlüssel",
-- master keys
masterKeysTitle = "Hauptschlüssel",
masterKeysDesc = "~g~Zeigt alle deine Hauptschlüssel an",
-- additional keys
keysTitle = "Ersatzschlüssel",
keysDesc = "~g~Zeigt alle deine Ersatzschlüssel an",
giveKeyTitle = "~g~Ersatzschlüssel Geben",
giveKeyDesc = "~g~Ersatzschlüssel Geben",
removeKeyTitle = "~r~Schlüssel Entfernen",
removeKeyDesc = "~r~Schlüssel Entfernen",
transferVehicleTitle = "~g~Fahrzeug übergeben",
transferVehicleDesc = "~g~Übertrage ein Fahrzeug an einen Spieler in der Nähe",
transferVehicleSuccess = "~g~Fahrzeug erfolgreich übergeben an %s",
transferVehicleFailed = "~r~Fahrzeugübergabe fehlgeschlagen",
transferVehicleReceived = "~g~Du hast ein Fahrzeug erhalten",
noPlayersNearby = "~r~Keine Spieler in der Nähe",
},
-- ContextMenu
CM = {
-- keymaker menu
createKeyTitle = "Ersatzschlüssel",
invalKeyTitle = "Schlossaustauschen",
-- key inventory
masterKeysTitle = "~g~Hauptschlüssel",
keysTitle = "~b~Ersatzschlüssel",
-- other player
giveKey = "~r~Gebe ein Schlüssel, an der Person",
transferVehicleTitle = "Fahrzeug übergeben",
}
}

View file

@ -0,0 +1,57 @@
fx_version 'cerulean'
games { 'gta5' }
author 'Mîhó'
description 'Manage vehicle keys!'
version '1.1.1'
dependency 'kimi_callbacks'
dependency 'NativeUI'
server_scripts {
--'@mysql-async/lib/MySQL.lua',
'@oxmysql/lib/MySQL.lua',
'config.lua',
'server.lua'
}
client_scripts {
-- comment these lines if not using the ContextMenu
--'@ContextMenu/screenToWorld.lua',
--'@ContextMenu/Drawables/Color.lua',
--'@ContextMenu/Drawables/Rect.lua',
--'@ContextMenu/Drawables/Text.lua',
--'@ContextMenu/Drawables/Sprite.lua',
--'@ContextMenu/Menu/Item.lua',
--'@ContextMenu/Menu/TextItem.lua',
--'@ContextMenu/Menu/CheckboxItem.lua',
--'@ContextMenu/Menu/SubmenuItem.lua',
--'@ContextMenu/Menu/Separator.lua',
--'@ContextMenu/Menu/Border.lua',
--'@ContextMenu/Menu/Menu.lua',
--'@ContextMenu/Menu/MenuPool.lua',
-- comment this line if not using NativeUILua
'@NativeUI/NativeUI.lua',
'@ox_lib/init.lua',
'config.lua',
'client.lua'
}
server_exports {
'IsVehicleOwner',
'IsKeyOwner',
'IsVehicleOrKeyOwner',
'GetPlayerKeys',
'GetPlayerVehicles'
}
exports {
'IsVehicleOwner',
'IsKeyOwner',
'IsVehicleOrKeyOwner',
'GetAllKeys',
'GetAllVehicles'
}

View file

@ -0,0 +1,503 @@
local CB = exports["kimi_callbacks"]
local QBCore = exports['qb-core']:GetCoreObject()
-- create a new key
CB:Register("VKC:createNewKey", function(source, plate, count)
local src = source
if (plate == nil or count == nil) then
print("^1[ERROR] \"plate\" or \"count\" was nil while creating new key for id " .. tostring(src))
return false
end
local Player = QBCore.Functions.GetPlayer(src)
if (Player) then
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
if (Player.Functions.GetMoney('cash') >= Config.Costs.newKey) then
local results = MySQL.Sync.fetchAll("SELECT count FROM vehicle_keys WHERE owner = @owner AND (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
local rows = 0
if (#results > 0) then
rows = MySQL.Sync.execute("UPDATE vehicle_keys SET count = count + @count WHERE owner = @owner AND (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate,
["@count"] = count
})
else
rows = MySQL.Sync.execute("INSERT INTO vehicle_keys (owner, plate, count) VALUES (@owner, @trimmedPlate, @count)", {
["@owner"] = Player.PlayerData.citizenid,
["@trimmedPlate"] = trimmedPlate,
["@count"] = count
})
end
if (rows == 0) then
return false
end
Player.Functions.RemoveMoney('cash', Config.Costs.newKey)
else
return "noMoney"
end
else
print("^1[ERROR] \"playerData\" was nil while creating new key for id " .. tostring(src))
return false
end
return true
end)
-- remove a key from a plate
CB:Register("VKC:removeKey", function(source, plate, num)
local src = source
if (plate == nil or num == nil) then
print("^1[ERROR] \"plate\" or \"num\" was nil while removing a key for id " .. tostring(src))
return false
end
local Player = QBCore.Functions.GetPlayer(src)
if (Player) then
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
local rows = MySQL.Sync.execute("DELETE FROM vehicle_keys WHERE owner = @owner and (plate = @plate OR plate = @trimmedPlate) and count = @num", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate,
["@num"] = num
})
if (rows == 0) then
rows = MySQL.Sync.execute("UPDATE vehicle_keys SET count = count - @num WHERE owner = @owner and (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate,
["@num"] = num
})
end
if (rows == 0) then
return false
end
else
print("^1[ERROR] \"playerData\" was nil while removing a key for id " .. tostring(src))
return false
end
return true
end)
-- remove a key from a plate
CB:Register("VKC:giveKeyToPlayer", function(source, plate, playerId)
local src = source
if (plate == nil or playerId == nil) then
print("^1[ERROR] \"plate\" or \"playerId\" was nil while giving a key for id " .. tostring(src))
return false
end
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
local Playera = QBCore.Functions.GetPlayer(src) --MARK
local Playerb = QBCore.Functions.GetPlayer(playerId)
if (Playera and Playerb) then
local rows = MySQL.Sync.execute("DELETE FROM vehicle_keys WHERE owner = @owner and (plate = @plate OR plate = @trimmedPlate) and count = 1", {
["@owner"] = Playera.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
if (rows == 0) then
rows = MySQL.Sync.execute("UPDATE vehicle_keys SET count = count - 1 WHERE owner = @owner and (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Playera.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
end
if (rows == 0) then
return false
else
local results = MySQL.Sync.fetchAll("SELECT count FROM vehicle_keys WHERE owner = @owner AND (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Playerb.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
rows = 0
if (#results > 0) then
rows = MySQL.Sync.execute("UPDATE vehicle_keys SET count = count + 1 WHERE owner = @owner AND (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Playerb.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
else
rows = MySQL.Sync.execute("INSERT INTO vehicle_keys (owner, plate, count) VALUES (@owner, @trimmedPlate, 1)", {
["@owner"] = Playerb.PlayerData.citizenid,
["@trimmedPlate"] = trimmedPlate
})
end
if (rows == 0) then
return false
end
end
else
print("^1[ERROR] \"playerData\" or \"playerData2\" was nil while giving a key for id " .. tostring(src))
return false
end
TriggerClientEvent("VKC:giveKeyNotif", playerId, trimmedPlate)
return true
end)
-- remove all keys from a plate
CB:Register("VKC:removeAllKeys", function(source, plate)
local src = source
if (plate == nil) then
print("^1[ERROR] \"plate\" was nil while removing all keys for " .. tostring(src))
return false
end
local Player = QBCore.Functions.GetPlayer(src)
if (Player) then
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
if (Player.Functions.GetMoney(Config.Costs.GetMoney) >= Config.Costs.exchangeLocks) then
MySQL.Sync.execute("DELETE FROM vehicle_keys WHERE plate = @trimmedPlate", {
["@trimmedPlate"] = trimmedPlate
})
Player.Functions.RemoveMoney(Config.Costs.GetMoney, Config.Costs.exchangeLocks)
return true
else
return "noMoney"
end
end
return false
end)
-- get all owned vehicles from player
function GetPlayerVehicleData(playerId)
if (playerId == nil) then
print("^1[ERROR] Parameter \"playerId\" was nil while triggering server export \"GetOwnedVehicles\"!")
return
end
local Player = QBCore.Functions.GetPlayer(playerId)
local vehicles = {}
if (Player) then
local results = MySQL.Sync.fetchAll("SELECT plate, vehicle FROM player_vehicles WHERE citizenid = @owner", {
["@owner"] = Player.PlayerData.citizenid
})
for i = 1, #results, 1 do
print(json.encode(results[i]))
table.insert(vehicles, {
results[i].plate,
results[i].vehicle})
--json.decode(results[i].vehicles).model})
end
else
print("^1[ERROR] \"playerData\" was nil while getting owned vehicles for id " .. tostring(playerId))
end
print(json.encode(vehicles))
return vehicles
end
CB:Register("VKC:getPlayerVehicleData", GetPlayerVehicleData)
-- get all owned keys from player
function GetPlayerKeys(playerId)
if (playerId == nil) then
print("^1[ERROR] Parameter \"playerId\" was nil while triggering server export \"GetOwnedKeys\"!")
return
end
local keys = {}
local Player = QBCore.Functions.GetPlayer(playerId)
if (Player) then
local results = MySQL.Sync.fetchAll("SELECT vehicle_keys.plate, vehicle_keys.count, player_vehicles.vehicle FROM vehicle_keys INNER JOIN player_vehicles ON vehicle_keys.plate = player_vehicles.plate WHERE vehicle_keys.owner = @owner", {
["@owner"] = Player.PlayerData.citizenid
})
for i = 1, #results, 1 do
table.insert(keys, {
plate = results[i].plate,
count = results[i].count,
model = results[i].vehicle --json.decode(results[i].vehicle).model
})
end
else
print("^1[ERROR] \"playerData\" was nil while getting owned keys for id " .. tostring(playerId))
end
return keys
end
CB:Register("VKC:getPlayerKeys", GetPlayerKeys)
-- return if playerId is owner of vehicle
function IsVehicleOwner(playerId, plate)
if (playerId == nil) then
print("^1[ERROR] Parameter \"playerId\" was nil while triggering server export \"IsVehicleOwner\"!")
return
end
if (plate == nil) then
print("^1[ERROR] Parameter \"plate\" was nil while triggering server export \"IsVehicleOwner\"!")
return
end
local Player = QBCore.Functions.GetPlayer(playerId)
if (Player) then
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
local results = MySQL.Sync.fetchAll("SELECT plate FROM player_vehicles WHERE citizenid = @owner and (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
if (#results > 0) then
return true
end
else
print("^1[ERROR] \"playerData\" was nil while getting vehicle ownership for id " .. tostring(playerId))
end
return false
end
CB:Register("VKC:isVehicleOwner", IsVehicleOwner)
-- return if playerId is owner of key
function IsKeyOwner(playerId, plate, model)
if (playerId == nil) then
print("^1[ERROR] Parameter \"playerId\" was nil while triggering server export \"IsKeyOwner\"!")
return
end
if (plate == nil) then
print("^1[ERROR] Parameter \"plate\" was nil while triggering server export \"IsKeyOwner\"!")
return
end
if (model == nil) then
print("^1[ERROR] Parameter \"model\" was nil while triggering server export \"IsKeyOwner\"!")
return
end
local Player = QBCore.Functions.GetPlayer(playerId)
if (Player) then
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
if (IsJobVehicle(Player.PlayerData.job.name, trimmedPlate, model)) then
return true
end
local results = MySQL.Sync.fetchAll("SELECT plate FROM vehicle_keys WHERE owner = @owner and (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
if (#results > 0) then
return true
end
else
print("^1[ERROR] \"playerData\" was nil while getting key ownership for id " .. tostring(playerId))
end
return false
end
exports["kimi_callbacks"]:Register("VKC:isKeyOwner", IsKeyOwner)
-- return if playerId is owner of vehicle or key
function IsVehicleOrKeyOwner(playerId, plate, model)
if (playerId == nil) then
print("^1[ERROR] Parameter \"playerId\" was nil while triggering server export \"IsVehicleOrKeyOwner\"!")
return
end
if (plate == nil) then
print("^1[ERROR] Parameter \"plate\" was nil while triggering server export \"IsVehicleOrKeyOwner\"!")
return
end
if (model == nil) then
print("^1[ERROR] Parameter \"model\" was nil while triggering server export \"IsVehicleOrKeyOwner\"!")
return
end
local Player = QBCore.Functions.GetPlayer(playerId)
if (Player) then
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
if (IsJobVehicle(Player.PlayerData.job.name, trimmedPlate, model)) then
return true
end
local results = MySQL.Sync.fetchAll("SELECT plate FROM player_vehicles WHERE citizenid = @owner and (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
if (#results > 0) then
return true
end
results = MySQL.Sync.fetchAll("SELECT plate FROM vehicle_keys WHERE owner = @owner and (plate = @plate OR plate = @trimmedPlate)", {
["@owner"] = Player.PlayerData.citizenid,
["@plate"] = plate,
["@trimmedPlate"] = trimmedPlate
})
if (#results > 0) then
return true
end
else
print("^1[ERROR] \"playerData\" was nil while getting vehicle or key ownership for id " .. tostring(playerId))
end
return false
end
CB:Register("VKC:isVehicleOrKeyOwner", IsVehicleOrKeyOwner)
-- if the given vehicle is a job vehicle
function IsJobVehicle(job, plate, model)
local jobData = Config.JobVehicles[job]
if (jobData == nil) then
return false
end
for i, m in ipairs(jobData.models) do
if (m == model) then
return true
end
end
for i, p in ipairs(jobData.plates) do
if (plate:find(p:upper())) then
return true
end
end
return false
end
-- toggle door lock over network to ensure it always works
RegisterServerEvent("VKC:toggleLockNet")
AddEventHandler("VKC:toggleLockNet", function(vehicleNetId, unlocked)
local vehicle = NetworkGetEntityFromNetworkId(vehicleNetId)
if (DoesEntityExist(vehicle)) then
local entityOwner = NetworkGetEntityOwner(vehicle)
TriggerClientEvent("VKC:toggleLockOnPlayer", entityOwner, vehicleNetId, unlocked)
end
end)
RegisterServerEvent("VKC:playDoorLockSoundNet")
AddEventHandler("VKC:playDoorLockSoundNet", function(vehicleNetId, lock)
TriggerClientEvent("VKC:playDoorLockSound", -1, vehicleNetId, lock)
end)
RegisterServerEvent('VKC:setvehkey')
AddEventHandler('VKC:setvehkey', function(plate)
local _source = source
local xPlayer = QBCore.Functions.GetPlayer(_source)
MySQL.query("INSERT INTO vehicle_keys(owner, plate, count) VALUES (?, ?, ?)", {xPlayer.PlayerData.citizenid, plate, 1})
end)
RegisterServerEvent('VKC:delvehkey')
AddEventHandler('VKC:delvehkey', function(plate, owner)
local _source = source
if owner then
local xPlayer = QBCore.Functions.GetPlayer(_source)
MySQL.query("DELETE FROM vehicle_keys WHERE plate = ? and owner = ?", {plate, xPlayer.PlayerData.citizenid})
else
MySQL.query("DELETE FROM vehicle_keys WHERE plate = ?", {plate})
end
end)
-- Fahrzeugbesitz an einen anderen Spieler übertragen
CB:Register("VKC:transferVehicleOwnership", function(source, plate, targetPlayerId)
local src = source
if (plate == nil or targetPlayerId == nil) then
print("^1[ERROR] \"plate\" or \"targetPlayerId\" was nil while transferring vehicle ownership for id " .. tostring(src))
return false
end
local Player = QBCore.Functions.GetPlayer(src)
local TargetPlayer = QBCore.Functions.GetPlayer(targetPlayerId)
if (not Player or not TargetPlayer) then
print("^1[ERROR] Player or target player not found while transferring vehicle ownership")
return false
end
local trimmedPlate = plate:gsub("^%s*(.-)%s*$", "%1"):upper()
-- Überprüfen, ob der Spieler das Fahrzeug besitzt
local isOwner = IsVehicleOwner(src, trimmedPlate)
if not isOwner then
return false
end
-- Fahrzeugmodell für die Benachrichtigung abrufen
local vehicleModel = nil
local results = MySQL.Sync.fetchAll("SELECT vehicle FROM player_vehicles WHERE plate = @plate AND citizenid = @owner", {
["@plate"] = trimmedPlate,
["@owner"] = Player.PlayerData.citizenid
})
if #results > 0 then
vehicleModel = results[1].vehicle
end
-- Besitz in der Datenbank übertragen
local success = MySQL.Sync.execute("UPDATE player_vehicles SET citizenid = @newOwner WHERE plate = @plate AND citizenid = @oldOwner", {
["@newOwner"] = TargetPlayer.PlayerData.citizenid,
["@plate"] = trimmedPlate,
["@oldOwner"] = Player.PlayerData.citizenid
})
if success > 0 then
-- Alle Schlüssel auf den neuen Besitzer übertragen
MySQL.Sync.execute("DELETE FROM vehicle_keys WHERE plate = @plate", {
["@plate"] = trimmedPlate
})
-- Einen Schlüssel dem neuen Besitzer geben
MySQL.Sync.execute("INSERT INTO vehicle_keys (owner, plate, count) VALUES (@owner, @plate, 1)", {
["@owner"] = TargetPlayer.PlayerData.citizenid,
["@plate"] = trimmedPlate
})
-- Den Zielspieler benachrichtigen
TriggerClientEvent("VKC:vehicleTransferNotif", targetPlayerId, trimmedPlate, vehicleModel)
return true
end
return false
end)

View file

@ -0,0 +1,27 @@
-- --------------------------------------------------------
-- Host: 127.0.0.1
-- Server version: 10.1.28-MariaDB - mariadb.org binary distribution
-- Server OS: Win32
-- HeidiSQL Version: 11.0.0.5919
-- --------------------------------------------------------
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET NAMES utf8 */;
/*!50503 SET NAMES utf8mb4 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
-- Dumping structure for table essentialmode.vehicle_keys
CREATE TABLE IF NOT EXISTS `vehicle_keys` (
`owner` varchar(60) NOT NULL,
`plate` varchar(8) NOT NULL,
`count` smallint(6) NOT NULL,
KEY `plate` (`plate`),
KEY `owner` (`owner`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- Data exporting was unselected.
/*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */;
/*!40014 SET FOREIGN_KEY_CHECKS=IF(@OLD_FOREIGN_KEY_CHECKS IS NULL, 1, @OLD_FOREIGN_KEY_CHECKS) */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;