-- ═══════════════════════════════════════════════════════════════ -- MercyV Bike – Server -- ═══════════════════════════════════════════════════════════════ local ESX = nil Citizen.CreateThread(function() if Config.NewESX then ESX = exports['es_extended']:getSharedObject() end end) local function GetIdentifier(src) local xp = ESX and ESX.GetPlayerFromId(src) return xp and xp.identifier or nil end local function IsAdmin(src) if IsPlayerAceAllowed(src, Config.AdminAce) then return true end if ESX then local xp = ESX.GetPlayerFromId(src) if xp then local g = xp.getGroup() if g == "admin" or g == "superadmin" then return true end end end return false end -- ── Tabelle erstellen ────────────────────────────────────────── AddEventHandler('onResourceStart', function(res) if res ~= GetCurrentResourceName() then return end Wait(1000) MySQL.query.await([[ CREATE TABLE IF NOT EXISTS `mercyv_bike_claims` ( `identifier` VARCHAR(120) NOT NULL, `bike_model` VARCHAR(60) NOT NULL, `claimed_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`identifier`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ]]) print('^2[mercyv-bike]^0 Tabelle bereit.') end) -- ── NPC-Position synchronisieren ────────────────────────────── local NPCData = nil local SpawnData = nil -- separate Spawn-Position für Fahrräder AddEventHandler('onResourceStart', function(res) if res ~= GetCurrentResourceName() then return end Wait(1500) -- Tabelle ZUERST anlegen MySQL.query.await([[ CREATE TABLE IF NOT EXISTS `mercyv_bike_npc` ( `id` INT AUTO_INCREMENT PRIMARY KEY, `model` VARCHAR(100) DEFAULT 'a_m_m_beach_01', `x` FLOAT DEFAULT 0, `y` FLOAT DEFAULT 0, `z` FLOAT DEFAULT 0, `heading` FLOAT DEFAULT 0, `spawn_x` FLOAT DEFAULT 0, `spawn_y` FLOAT DEFAULT 0, `spawn_z` FLOAT DEFAULT 0, `spawn_heading` FLOAT DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ]]) -- Dann lesen local r = MySQL.query.await("SELECT * FROM mercyv_bike_npc LIMIT 1") if r and r[1] then NPCData = r[1] if r[1].spawn_x and r[1].spawn_x ~= 0 then SpawnData = { x = r[1].spawn_x, y = r[1].spawn_y, z = r[1].spawn_z, heading = r[1].spawn_heading or 0 } end else NPCData = { model = Config.NPC.model, x = Config.NPC.x, y = Config.NPC.y, z = Config.NPC.z, heading = Config.NPC.heading, } end print('^2[mercyv-bike]^0 NPC-Daten geladen.') end) -- ── Client ready ─────────────────────────────────────────────── RegisterNetEvent('mercyv-bike:clientReady', function() local src = source Citizen.CreateThread(function() Wait(500) TriggerClientEvent('mercyv-bike:syncNPC', src, NPCData) Wait(200) TriggerClientEvent('mercyv-bike:setAdminStatus', src, IsAdmin(src)) end) end) RegisterNetEvent('mercyv-bike:checkAdmin', function() TriggerClientEvent('mercyv-bike:setAdminStatus', source, IsAdmin(source)) end) -- ── Fahrrad abholen ──────────────────────────────────────────── RegisterNetEvent('mercyv-bike:claim', function(bikeModel) local src = source local identifier = GetIdentifier(src) if not identifier then return end -- Prüfen ob bereits eines abgeholt local existing = MySQL.query.await( 'SELECT identifier FROM mercyv_bike_claims WHERE identifier = ?', { identifier } ) if existing and existing[1] then Config.ServerNotification(src, Config.Notify.ALREADY_CLAIMED, "error") return end -- Eintragen MySQL.insert( 'INSERT INTO mercyv_bike_claims (identifier, bike_model) VALUES (?, ?)', { identifier, bikeModel } ) -- Spawn-Signal TriggerClientEvent('mercyv-bike:doSpawn', src, bikeModel, SpawnData) Config.ServerNotification(src, Config.Notify.CLAIMED, "success") print(string.format('[mercyv-bike] %s hat %s erhalten.', identifier, bikeModel)) end) -- ── Prüfen ob Spieler bereits eines hat ─────────────────────── RegisterNetEvent('mercyv-bike:checkClaim', function() local src = source local identifier = GetIdentifier(src) if not identifier then TriggerClientEvent('mercyv-bike:claimStatus', src, false) return end local r = MySQL.query.await( 'SELECT bike_model FROM mercyv_bike_claims WHERE identifier = ?', { identifier } ) TriggerClientEvent('mercyv-bike:claimStatus', src, r and r[1] ~= nil, r and r[1] and r[1].bike_model) end) -- ── Admin: NPC-Position speichern ───────────────────────────── RegisterNetEvent('mercyv-bike:saveNPC', function(data) local src = source if not IsAdmin(src) then Config.ServerNotification(src, Config.Notify.NO_ACCESS, "error") return end NPCData = data if data.spawn_x and data.spawn_x ~= 0 then SpawnData = { x = data.spawn_x, y = data.spawn_y, z = data.spawn_z, heading = data.spawn_heading or 0 } end -- In DB speichern local existing = MySQL.query.await('SELECT id FROM mercyv_bike_npc LIMIT 1') local sx = data.spawn_x or 0 local sy = data.spawn_y or 0 local sz = data.spawn_z or 0 local sh = data.spawn_heading or 0 if existing and existing[1] then MySQL.update('UPDATE mercyv_bike_npc SET model=?, x=?, y=?, z=?, heading=?, spawn_x=?, spawn_y=?, spawn_z=?, spawn_heading=? WHERE id=?', { data.model, data.x, data.y, data.z, data.heading, sx, sy, sz, sh, existing[1].id }) else MySQL.insert('INSERT INTO mercyv_bike_npc (model, x, y, z, heading, spawn_x, spawn_y, spawn_z, spawn_heading) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)', { data.model, data.x, data.y, data.z, data.heading, sx, sy, sz, sh }) end TriggerClientEvent('mercyv-bike:syncNPC', -1, NPCData) Config.ServerNotification(src, "NPC-Position gespeichert.", "success") end) -- ── Benachrichtigung ────────────────────────────────────────── RegisterNetEvent('mercyv-bike:notify', function(msg, type) -- Wird clientseitig behandelt end) -- ── Admin: Claim zurücksetzen ───────────────────────────────── RegisterCommand('bikereset', function(src, args) if not IsAdmin(src) then return end local targetId = tonumber(args[1]) if not targetId then print('[mercyv-bike] Verwendung: /bikereset [Spieler-ID]') return end local xp = ESX and ESX.GetPlayerFromId(targetId) if not xp then return end MySQL.update('DELETE FROM mercyv_bike_claims WHERE identifier = ?', { xp.identifier }) Config.ServerNotification(src, 'Claim von Spieler ' .. targetId .. ' zurückgesetzt.', 'success') Config.ServerNotification(targetId, 'Dein Fahrrad-Claim wurde zurückgesetzt.', 'info') end, false)