-- ═══════════════════════════════════════════════════════════════ -- 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 -- wird aus DB oder Config geladen AddEventHandler('onResourceStart', function(res) if res ~= GetCurrentResourceName() then return end Wait(1500) -- NPC-Position aus DB laden falls gespeichert local r = MySQL.query.await("SELECT * FROM mercyv_bike_npc LIMIT 1") if r and r[1] then NPCData = r[1] else NPCData = { model = Config.NPC.model, x = Config.NPC.x, y = Config.NPC.y, z = Config.NPC.z, heading = Config.NPC.heading, } end -- NPC-Tabelle anlegen falls nicht vorhanden 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ]]) TriggerClientEvent('mercyv-bike:syncNPC', -1, NPCData) 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) 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 -- In DB speichern local existing = MySQL.query.await('SELECT id FROM mercyv_bike_npc LIMIT 1') if existing and existing[1] then MySQL.update('UPDATE mercyv_bike_npc SET model=?, x=?, y=?, z=?, heading=? WHERE id=?', { data.model, data.x, data.y, data.z, data.heading, existing[1].id }) else MySQL.insert('INSERT INTO mercyv_bike_npc (model, x, y, z, heading) VALUES (?, ?, ?, ?, ?)', { data.model, data.x, data.y, data.z, data.heading }) 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)