From 99aed29a526d8a350811da466e0506b7ec88dc47 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Sat, 26 Jul 2025 00:28:59 +0200 Subject: [PATCH] ed --- .../[carscripts]/nordi_rental/client.lua | 175 +++++++++++++++++ .../[carscripts]/nordi_rental/config.lua | 62 ++++++ .../[carscripts]/nordi_rental/fxmanifest.lua | 0 .../[carscripts]/nordi_rental/server.lua | 178 ++++++++++++++++++ 4 files changed, 415 insertions(+) create mode 100644 resources/[carscripts]/nordi_rental/client.lua create mode 100644 resources/[carscripts]/nordi_rental/config.lua create mode 100644 resources/[carscripts]/nordi_rental/fxmanifest.lua create mode 100644 resources/[carscripts]/nordi_rental/server.lua diff --git a/resources/[carscripts]/nordi_rental/client.lua b/resources/[carscripts]/nordi_rental/client.lua new file mode 100644 index 000000000..591ee183f --- /dev/null +++ b/resources/[carscripts]/nordi_rental/client.lua @@ -0,0 +1,175 @@ +local QBCore = exports['qb-core']:GetCoreObject() +local spawnedNPCs = {} +local currentRental = nil + +-- NPCs spawnen +CreateThread(function() + for i = 1, #Config.RentalLocations do + local location = Config.RentalLocations[i] + + RequestModel(location.npc.model) + while not HasModelLoaded(location.npc.model) do + Wait(1) + end + + local npc = CreatePed(4, location.npc.model, location.npc.coords.x, location.npc.coords.y, location.npc.coords.z - 1.0, location.npc.coords.w, false, true) + FreezeEntityPosition(npc, true) + SetEntityInvincible(npc, true) + SetBlockingOfNonTemporaryEvents(npc, true) + + spawnedNPCs[location.id] = npc + + -- QB-Target für NPC + exports['qb-target']:AddTargetEntity(npc, { + options = { + { + type = "client", + event = "vehiclerental:client:openMenu", + icon = "fas fa-car", + label = "Fahrzeug mieten", + locationId = location.id + }, + { + type = "client", + event = "vehiclerental:client:returnVehicle", + icon = "fas fa-car-side", + label = "Fahrzeug zurückgeben", + locationId = location.id + } + }, + distance = 2.0 + }) + end +end) + +-- Mietmenü öffnen +RegisterNetEvent('vehiclerental:client:openMenu', function(data) + local locationId = data.locationId + local location = nil + + for i = 1, #Config.RentalLocations do + if Config.RentalLocations[i].id == locationId then + location = Config.RentalLocations[i] + break + end + end + + if not location then return end + + local options = {} + for i = 1, #location.vehicles do + local vehicle = location.vehicles[i] + table.insert(options, { + title = vehicle.label, + description = '$' .. vehicle.price .. ' pro Stunde', + icon = 'car', + onSelect = function() + openRentalDialog(vehicle, location) + end + }) + end + + lib.registerContext({ + id = 'vehicle_rental_menu', + title = location.name, + options = options + }) + + lib.showContext('vehicle_rental_menu') +end) + +-- Mietdialog +function openRentalDialog(vehicle, location) + local input = lib.inputDialog('Fahrzeug mieten', { + { + type = 'number', + label = 'Mietdauer (Stunden)', + description = 'Maximale Mietdauer: ' .. Config.MaxRentalTime .. ' Stunden', + required = true, + min = 1, + max = Config.MaxRentalTime + } + }) + + if not input or not input[1] then return end + + local hours = tonumber(input[1]) + if not hours or hours < 1 or hours > Config.MaxRentalTime then + QBCore.Functions.Notify('Ungültige Mietdauer!', 'error') + return + end + + local totalCost = vehicle.price * hours + local plate = GeneratePlate() + + QBCore.Functions.TriggerCallback('vehiclerental:server:rentVehicle', function(success) + if success then + spawnRentalVehicle(vehicle.model, location.spawnPoint, plate) + end + end, { + vehicleModel = vehicle.model, + pricePerHour = vehicle.price, + hours = hours, + locationId = location.id, + plate = plate + }) +end) + +-- Fahrzeug spawnen +function spawnRentalVehicle(model, spawnPoint, plate) + RequestModel(model) + while not HasModelLoaded(model) do + Wait(1) + end + + local vehicle = CreateVehicle(model, spawnPoint.x, spawnPoint.y, spawnPoint.z, spawnPoint.w, true, false) + SetVehicleNumberPlateText(vehicle, plate) + SetEntityAsMissionEntity(vehicle, true, true) + TaskWarpPedIntoVehicle(PlayerPedId(), vehicle, -1) + + TriggerEvent("vehiclekeys:client:SetOwner", plate) + SetModelAsNoLongerNeeded(model) +end) + +-- Fahrzeug zurückgeben +RegisterNetEvent('vehiclerental:client:returnVehicle', function(data) + local ped = PlayerPedId() + local vehicle = GetVehiclePedIsIn(ped, false) + + if vehicle == 0 then + QBCore.Functions.Notify('Du musst in einem Fahrzeug sitzen!', 'error') + return + end + + local plate = GetVehicleNumberPlateText(vehicle) + + QBCore.Functions.TriggerCallback('vehiclerental:server:returnVehicle', function(success) + if success then + DeleteVehicle(vehicle) + end + end, plate) +end) + +-- Kennzeichen generieren +function GeneratePlate() + local plate = "" + for i = 1, 8 do + if math.random(1, 2) == 1 then + plate = plate .. string.char(math.random(65, 90)) -- A-Z + else + plate = plate .. tostring(math.random(0, 9)) -- 0-9 + end + end + return plate +end + +-- Cleanup beim Ressourcen-Stop +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() ~= resourceName then return end + + for _, npc in pairs(spawnedNPCs) do + if DoesEntityExist(npc) then + DeleteEntity(npc) + end + end +end) diff --git a/resources/[carscripts]/nordi_rental/config.lua b/resources/[carscripts]/nordi_rental/config.lua new file mode 100644 index 000000000..98dc22551 --- /dev/null +++ b/resources/[carscripts]/nordi_rental/config.lua @@ -0,0 +1,62 @@ +Config = {} + +Config.MaxRentalTime = 24 -- Maximale Mietdauer in Stunden +Config.PenaltyPerHour = 100 -- Strafe pro Stunde Verspätung +Config.UseOkokBanking = true -- true für okokBanking, false für Bargeld + +Config.RentalLocations = { + { + id = "rental_1", + name = "Downtown Autovermietung", + npc = { + model = "a_m_m_business_01", + coords = vector4(-56.79, -1096.58, 26.42, 25.0), + }, + spawnPoint = vector4(-66.79, -1096.58, 26.42, 25.0), + returnPoint = vector4(-66.79, -1096.58, 26.42, 25.0), + vehicles = { + { + model = "blista", + label = "Blista", + price = 50, -- Preis pro Stunde + category = "compacts" + }, + { + model = "sultan", + label = "Sultan", + price = 75, + category = "sedans" + }, + { + model = "baller", + label = "Baller", + price = 100, + category = "suvs" + } + } + }, + { + id = "rental_2", + name = "Sandy Shores Autovermietung", + npc = { + model = "a_m_m_business_01", + coords = vector4(1737.59, 3710.2, 34.14, 25.0), + }, + spawnPoint = vector4(1727.59, 3710.2, 34.14, 25.0), + returnPoint = vector4(1727.59, 3710.2, 34.14, 25.0), + vehicles = { + { + model = "rebel", + label = "Rebel", + price = 80, + category = "offroad" + }, + { + model = "sandking", + label = "Sandking", + price = 120, + category = "offroad" + } + } + } +} diff --git a/resources/[carscripts]/nordi_rental/fxmanifest.lua b/resources/[carscripts]/nordi_rental/fxmanifest.lua new file mode 100644 index 000000000..e69de29bb diff --git a/resources/[carscripts]/nordi_rental/server.lua b/resources/[carscripts]/nordi_rental/server.lua new file mode 100644 index 000000000..582f214eb --- /dev/null +++ b/resources/[carscripts]/nordi_rental/server.lua @@ -0,0 +1,178 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- Datenbank Tabelle erstellen +MySQL.ready(function() + MySQL.Async.execute([[ + CREATE TABLE IF NOT EXISTS vehicle_rentals ( + id INT AUTO_INCREMENT PRIMARY KEY, + citizenid VARCHAR(50) NOT NULL, + vehicle_model VARCHAR(50) NOT NULL, + vehicle_plate VARCHAR(10) NOT NULL, + rental_location VARCHAR(50) NOT NULL, + start_time BIGINT NOT NULL, + end_time BIGINT NOT NULL, + price_per_hour INT NOT NULL, + returned BOOLEAN DEFAULT FALSE, + penalty_paid BOOLEAN DEFAULT FALSE, + INDEX(citizenid), + INDEX(vehicle_plate) + ) + ]]) +end) + +-- Fahrzeug mieten +QBCore.Functions.CreateCallback('vehiclerental:server:rentVehicle', function(source, cb, data) + local Player = QBCore.Functions.GetPlayer(source) + if not Player then return cb(false) end + + local totalCost = data.pricePerHour * data.hours + local currentTime = os.time() + local endTime = currentTime + (data.hours * 3600) + + -- Zahlung prüfen und abziehen + local paymentSuccess = false + if Config.UseOkokBanking then + local bankMoney = exports['okokBanking']:GetAccount(Player.PlayerData.citizenid) + if bankMoney and bankMoney >= totalCost then + exports['okokBanking']:RemoveMoney(Player.PlayerData.citizenid, totalCost) + paymentSuccess = true + end + else + if Player.Functions.RemoveMoney('cash', totalCost) then + paymentSuccess = true + end + end + + if not paymentSuccess then + TriggerClientEvent('QBCore:Notify', source, 'Nicht genug Geld!', 'error') + return cb(false) + end + + -- Fahrzeug in Datenbank eintragen + MySQL.Async.insert('INSERT INTO vehicle_rentals (citizenid, vehicle_model, vehicle_plate, rental_location, start_time, end_time, price_per_hour) VALUES (?, ?, ?, ?, ?, ?, ?)', { + Player.PlayerData.citizenid, + data.vehicleModel, + data.plate, + data.locationId, + currentTime, + endTime, + data.pricePerHour + }) + + TriggerClientEvent('QBCore:Notify', source, 'Fahrzeug erfolgreich gemietet für $' .. totalCost, 'success') + cb(true) +end) + +-- Fahrzeug zurückgeben +QBCore.Functions.CreateCallback('vehiclerental:server:returnVehicle', function(source, cb, plate) + local Player = QBCore.Functions.GetPlayer(source) + if not Player then return cb(false) end + + MySQL.Async.fetchAll('SELECT * FROM vehicle_rentals WHERE citizenid = ? AND vehicle_plate = ? AND returned = FALSE', { + Player.PlayerData.citizenid, + plate + }, function(result) + if not result[1] then + TriggerClientEvent('QBCore:Notify', source, 'Kein aktives Mietverhältnis für dieses Fahrzeug gefunden!', 'error') + return cb(false) + end + + local rental = result[1] + local currentTime = os.time() + local penalty = 0 + + -- Strafe berechnen wenn verspätet + if currentTime > rental.end_time then + local hoursLate = math.ceil((currentTime - rental.end_time) / 3600) + penalty = hoursLate * Config.PenaltyPerHour + end + + -- Strafe abziehen falls vorhanden + if penalty > 0 then + local penaltyPaid = false + if Config.UseOkokBanking then + local bankMoney = exports['okokBanking']:GetAccount(Player.PlayerData.citizenid) + if bankMoney and bankMoney >= penalty then + exports['okokBanking']:RemoveMoney(Player.PlayerData.citizenid, penalty) + penaltyPaid = true + end + else + if Player.Functions.RemoveMoney('cash', penalty) then + penaltyPaid = true + end + end + + if penaltyPaid then + TriggerClientEvent('QBCore:Notify', source, 'Verspätungsstrafe von $' .. penalty .. ' wurde abgezogen!', 'error') + else + TriggerClientEvent('QBCore:Notify', source, 'Nicht genug Geld für Verspätungsstrafe!', 'error') + return cb(false) + end + end + + -- Mietverhältnis als zurückgegeben markieren + MySQL.Async.execute('UPDATE vehicle_rentals SET returned = TRUE, penalty_paid = ? WHERE id = ?', { + penalty > 0, + rental.id + }) + + TriggerClientEvent('QBCore:Notify', source, 'Fahrzeug erfolgreich zurückgegeben!', 'success') + cb(true) + end) +end) + +-- Mietzeit abfragen +QBCore.Functions.CreateCallback('vehiclerental:server:getRentalInfo', function(source, cb) + local Player = QBCore.Functions.GetPlayer(source) + if not Player then return cb(nil) end + + MySQL.Async.fetchAll('SELECT * FROM vehicle_rentals WHERE citizenid = ? AND returned = FALSE', { + Player.PlayerData.citizenid + }, function(result) + if not result[1] then + return cb(nil) + end + + local rentals = {} + for i = 1, #result do + local rental = result[i] + local currentTime = os.time() + local timeLeft = rental.end_time - currentTime + + table.insert(rentals, { + vehicleModel = rental.vehicle_model, + plate = rental.vehicle_plate, + timeLeft = timeLeft, + isOverdue = timeLeft < 0 + }) + end + + cb(rentals) + end) +end) + +-- Befehl für Mietzeit +QBCore.Commands.Add('mietzeit', 'Zeige deine aktuelle Mietzeit an', {}, false, function(source, args) + QBCore.Functions.TriggerCallback('vehiclerental:server:getRentalInfo', source, function(rentals) + if not rentals or #rentals == 0 then + TriggerClientEvent('QBCore:Notify', source, 'Du hast keine aktiven Mietverhältnisse!', 'error') + return + end + + for i = 1, #rentals do + local rental = rentals[i] + local timeText = "" + + if rental.isOverdue then + local hoursOverdue = math.ceil(math.abs(rental.timeLeft) / 3600) + timeText = "Überfällig um " .. hoursOverdue .. " Stunden" + else + local hoursLeft = math.floor(rental.timeLeft / 3600) + local minutesLeft = math.floor((rental.timeLeft % 3600) / 60) + timeText = hoursLeft .. "h " .. minutesLeft .. "m verbleibend" + end + + TriggerClientEvent('QBCore:Notify', source, rental.vehicleModel .. " (" .. rental.plate .. "): " .. timeText, rental.isOverdue and 'error' or 'primary') + end + end) +end)