Auto-sync 2026-04-15 22:45

This commit is contained in:
root 2026-04-15 22:45:02 +02:00
parent a3d5af24ec
commit f148a720da
4 changed files with 100 additions and 46 deletions

View File

@ -136,33 +136,30 @@ end)
-- Admin: Aktuelle Position erfassen -- Admin: Aktuelle Position erfassen
RegisterNUICallback('capturePos', function(data, cb) RegisterNUICallback('capturePos', function(data, cb)
local field = data.field or 'npc'
ClosePanel() ClosePanel()
Citizen.Wait(300) Citizen.Wait(300)
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
Notify("Geh zur NPC-Position und drücke E.", "info") Notify("Geh zur Position und drücke E.", "info")
exports['hex_4_hud']:ShowHelpNotify("Position erfassen", "E") exports['hex_4_hud']:ShowHelpNotify("Position erfassen", "E")
Citizen.CreateThread(function() Citizen.CreateThread(function()
while true do while true do
Citizen.Wait(0) Citizen.Wait(0)
if IsControlJustPressed(0, 38) then -- E if IsControlJustPressed(0, 38) then
exports['hex_4_hud']:HideHelpNotify() exports['hex_4_hud']:HideHelpNotify()
local c = GetEntityCoords(PlayerPedId()) local coords = GetEntityCoords(PlayerPedId())
local h = GetEntityHeading(PlayerPedId()) local heading = GetEntityHeading(PlayerPedId())
-- Panel wieder öffnen mit erfassten Koordinaten
SetNuiFocus(true, true) SetNuiFocus(true, true)
exports['hex_4_hud']:HideHud(true) exports['hex_4_hud']:HideHud(true)
PanelOpen = true PanelOpen = true
SendNUIMessage({ SendNUIMessage({
action = "OPEN_ADMIN", action = "OPEN_ADMIN",
isAdmin = IsAdmin, isAdmin = IsAdmin,
captured = { x = c.x, y = c.y, z = c.z, heading = h }, captured = { x = coords.x, y = coords.y, z = coords.z, heading = heading, field = field },
}) })
break break
elseif IsControlJustPressed(0, 322) then -- ESC elseif IsControlJustPressed(0, 322) then
exports['hex_4_hud']:HideHelpNotify() exports['hex_4_hud']:HideHelpNotify()
OpenPanel() OpenPanel()
break break
@ -218,27 +215,47 @@ end)
-- ── Fahrrad spawnen ──────────────────────────────────────────── -- ── Fahrrad spawnen ────────────────────────────────────────────
RegisterNetEvent('mercyv-bike:doSpawn', function(bikeModel) RegisterNetEvent('mercyv-bike:doSpawn', function(bikeModel, spawnData)
print('[mercyv-bike] Spawne Fahrrad: ' .. tostring(bikeModel))
local model = GetHashKey(bikeModel) local model = GetHashKey(bikeModel)
if model == 0 then
Notify("Unbekanntes Fahrrad-Modell: " .. tostring(bikeModel), "error")
return
end
RequestModel(model) RequestModel(model)
local t = GetGameTimer() + 5000 local t = GetGameTimer() + 8000
while not HasModelLoaded(model) do while not HasModelLoaded(model) do
if GetGameTimer() > t then if GetGameTimer() > t then
Notify("Fahrrad konnte nicht gespawnt werden.", "error") Notify("Fahrrad-Modell konnte nicht geladen werden.", "error")
return return
end end
Citizen.Wait(100) Citizen.Wait(100)
end end
local ped = PlayerPedId() -- Spawn-Position: vom Server gesetzte Koordinaten oder vor Spieler
local coords = GetEntityCoords(ped) local sx, sy, sz, sh
local heading = GetEntityHeading(ped) if spawnData and spawnData.x and spawnData.x ~= 0 then
local rad = math.rad(heading) sx = spawnData.x
local spawnX = coords.x + math.sin(-rad) * Config.SpawnOffset sy = spawnData.y
local spawnY = coords.y + math.cos(-rad) * Config.SpawnOffset sz = spawnData.z
sh = spawnData.heading or 0
else
local ped = PlayerPedId()
local coords = GetEntityCoords(ped)
local heading = GetEntityHeading(ped)
local rad = math.rad(heading)
sx = coords.x + math.sin(-rad) * Config.SpawnOffset
sy = coords.y + math.cos(-rad) * Config.SpawnOffset
sz = coords.z
sh = heading
end
local bike = CreateVehicle(model, spawnX, spawnY, coords.z, heading, true, false) print(string.format('[mercyv-bike] Spawn bei %.1f, %.1f, %.1f', sx, sy, sz))
local tw = GetGameTimer() + 3000
local bike = CreateVehicle(model, sx, sy, sz, sh, true, false)
local tw = GetGameTimer() + 4000
while not DoesEntityExist(bike) do while not DoesEntityExist(bike) do
if GetGameTimer() > tw then break end if GetGameTimer() > tw then break end
Citizen.Wait(100) Citizen.Wait(100)
@ -248,12 +265,11 @@ RegisterNetEvent('mercyv-bike:doSpawn', function(bikeModel)
SetEntityAsMissionEntity(bike, true, true) SetEntityAsMissionEntity(bike, true, true)
SetVehicleEngineOn(bike, true, true, false) SetVehicleEngineOn(bike, true, true, false)
SetModelAsNoLongerNeeded(model) SetModelAsNoLongerNeeded(model)
-- Schlüssel geben TriggerServerEvent('vehicles_keys:selfGiveVehicleKeys', GetVehicleNumberPlateText(bike))
local ks = Config.VehicleKeySystem Notify("Fahrrad erhalten! Viel Spaß!", "success")
if ks == 'jaksam' then print('[mercyv-bike] Fahrrad erfolgreich gespawnt.')
TriggerServerEvent('vehicles_keys:selfGiveVehicleKeys', else
GetVehicleNumberPlateText(bike)) Notify("Fahrrad konnte nicht gespawnt werden.", "error")
end
end end
end) end)

View File

@ -285,8 +285,21 @@ body {
</button> </button>
</div> </div>
<div class="mb-form-section">
<div class="mb-form-label"><i class="fas fa-car"></i> Fahrrad Spawn-Position</div>
<div class="mb-pos-row">
<input type="number" v-model.number="npc.spawn_x" placeholder="X" class="mb-input mb-input-sm">
<input type="number" v-model.number="npc.spawn_y" placeholder="Y" class="mb-input mb-input-sm">
<input type="number" v-model.number="npc.spawn_z" placeholder="Z" class="mb-input mb-input-sm">
<input type="number" v-model.number="npc.spawn_heading" placeholder="Heading" class="mb-input mb-input-sm">
</div>
<button class="mb-capture-btn" v-on:click="captureSpawn()">
<i class="fas fa-crosshairs"></i> Spawn-Position erfassen
</button>
</div>
<button class="mb-save-btn" v-on:click="saveNPC()"> <button class="mb-save-btn" v-on:click="saveNPC()">
<i class="fas fa-save"></i> NPC speichern <i class="fas fa-save"></i> Speichern
</button> </button>
<div class="mb-admin-hint"> <div class="mb-admin-hint">
@ -358,7 +371,10 @@ var app = new Vue({
postNUI('close'); postNUI('close');
}, },
capturePos: function() { capturePos: function() {
postNUI('capturePos', {}); postNUI('capturePos', { field: 'npc' });
},
captureSpawn: function() {
postNUI('capturePos', { field: 'spawn' });
}, },
saveNPC: function() { saveNPC: function() {
postNUI('saveNPC', { postNUI('saveNPC', {
@ -387,10 +403,17 @@ window.addEventListener('message', function(event) {
app.showAdmin = true; app.showAdmin = true;
app.isAdmin = true; app.isAdmin = true;
if (msg.captured) { if (msg.captured) {
app.npc.x = Math.round(msg.captured.x * 100) / 100; var rx = Math.round(msg.captured.x * 100) / 100;
app.npc.y = Math.round(msg.captured.y * 100) / 100; var ry = Math.round(msg.captured.y * 100) / 100;
app.npc.z = Math.round(msg.captured.z * 100) / 100; var rz = Math.round(msg.captured.z * 100) / 100;
app.npc.heading = Math.round(msg.captured.heading * 100) / 100; var rh = Math.round(msg.captured.heading * 100) / 100;
if (msg.captured.field === 'spawn') {
app.npc.spawn_x = rx; app.npc.spawn_y = ry;
app.npc.spawn_z = rz; app.npc.spawn_heading = rh;
} else {
app.npc.x = rx; app.npc.y = ry;
app.npc.z = rz; app.npc.heading = rh;
}
} }
break; break;
case 'CLOSE': case 'CLOSE':

View File

@ -45,7 +45,8 @@ end)
-- ── NPC-Position synchronisieren ────────────────────────────── -- ── NPC-Position synchronisieren ──────────────────────────────
local NPCData = nil -- wird aus DB oder Config geladen local NPCData = nil
local SpawnData = nil -- separate Spawn-Position für Fahrräder
AddEventHandler('onResourceStart', function(res) AddEventHandler('onResourceStart', function(res)
if res ~= GetCurrentResourceName() then return end if res ~= GetCurrentResourceName() then return end
@ -53,18 +54,25 @@ AddEventHandler('onResourceStart', function(res)
-- Tabelle ZUERST anlegen -- Tabelle ZUERST anlegen
MySQL.query.await([[ MySQL.query.await([[
CREATE TABLE IF NOT EXISTS `mercyv_bike_npc` ( CREATE TABLE IF NOT EXISTS `mercyv_bike_npc` (
`id` INT AUTO_INCREMENT PRIMARY KEY, `id` INT AUTO_INCREMENT PRIMARY KEY,
`model` VARCHAR(100) DEFAULT 'a_m_m_beach_01', `model` VARCHAR(100) DEFAULT 'a_m_m_beach_01',
`x` FLOAT DEFAULT 0, `x` FLOAT DEFAULT 0,
`y` FLOAT DEFAULT 0, `y` FLOAT DEFAULT 0,
`z` FLOAT DEFAULT 0, `z` FLOAT DEFAULT 0,
`heading` 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 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
]]) ]])
-- Dann lesen -- Dann lesen
local r = MySQL.query.await("SELECT * FROM mercyv_bike_npc LIMIT 1") local r = MySQL.query.await("SELECT * FROM mercyv_bike_npc LIMIT 1")
if r and r[1] then if r and r[1] then
NPCData = r[1] 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 else
NPCData = { NPCData = {
model = Config.NPC.model, model = Config.NPC.model,
@ -117,7 +125,7 @@ RegisterNetEvent('mercyv-bike:claim', function(bikeModel)
) )
-- Spawn-Signal -- Spawn-Signal
TriggerClientEvent('mercyv-bike:doSpawn', src, bikeModel) TriggerClientEvent('mercyv-bike:doSpawn', src, bikeModel, SpawnData)
Config.ServerNotification(src, Config.Notify.CLAIMED, "success") Config.ServerNotification(src, Config.Notify.CLAIMED, "success")
print(string.format('[mercyv-bike] %s hat %s erhalten.', identifier, bikeModel)) print(string.format('[mercyv-bike] %s hat %s erhalten.', identifier, bikeModel))
end) end)
@ -148,15 +156,22 @@ RegisterNetEvent('mercyv-bike:saveNPC', function(data)
end end
NPCData = data 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 -- In DB speichern
local existing = MySQL.query.await('SELECT id FROM mercyv_bike_npc LIMIT 1') 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 if existing and existing[1] then
MySQL.update('UPDATE mercyv_bike_npc SET model=?, x=?, y=?, z=?, heading=? WHERE id=?', 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, existing[1].id }) { data.model, data.x, data.y, data.z, data.heading, sx, sy, sz, sh, existing[1].id })
else else
MySQL.insert('INSERT INTO mercyv_bike_npc (model, x, y, z, heading) VALUES (?, ?, ?, ?, ?)', 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 }) { data.model, data.x, data.y, data.z, data.heading, sx, sy, sz, sh })
end end
TriggerClientEvent('mercyv-bike:syncNPC', -1, NPCData) TriggerClientEvent('mercyv-bike:syncNPC', -1, NPCData)

View File

@ -703,7 +703,7 @@ AddEventHandler('mercyv-deathscreen:client:npcReviveTeleport', function()
local ped = PlayerPedId() local ped = PlayerPedId()
-- Kurz warten, damit der Spieler nach dem Revive sicher am Leben ist -- Kurz warten, damit der Spieler nach dem Revive sicher am Leben ist
Wait(500) Wait(200)
-- Bildschirm langsam schwarz ausblenden (Dauer: 1000 Millisekunden = 1 Sekunde) -- Bildschirm langsam schwarz ausblenden (Dauer: 1000 Millisekunden = 1 Sekunde)
DoScreenFadeOut(1000) DoScreenFadeOut(1000)