Compare commits
10 Commits
a8225bd0dd
...
8ab6376676
| Author | SHA1 | Date | |
|---|---|---|---|
| 8ab6376676 | |||
| 6164274576 | |||
| f14cfef22f | |||
| 43a039d098 | |||
| fb58059168 | |||
| a2ccf2ad11 | |||
| f148a720da | |||
| a3d5af24ec | |||
| c4aca0cedd | |||
| 76924ef4e2 |
@ -29,7 +29,7 @@ Config.KeyBind = {
|
|||||||
Config.Tebex = {
|
Config.Tebex = {
|
||||||
enabled = true, -- Tebex Redeem aktivieren/deaktivieren
|
enabled = true, -- Tebex Redeem aktivieren/deaktivieren
|
||||||
apiKey = 'uJKATjAeO2X6NNDYvpt6bESwi5qMNCD2', -- Tebex Secret Key (Plugin API)
|
apiKey = 'uJKATjAeO2X6NNDYvpt6bESwi5qMNCD2', -- Tebex Secret Key (Plugin API)
|
||||||
identifierType = 'steam', -- 'steam' oder 'license'
|
identifierType = 'license', -- 'steam' oder 'license'
|
||||||
storeUrl = 'https://mercyv.tebex.io', -- Tebex Store URL (wird geoeffnet wenn man auf Coins klickt)
|
storeUrl = 'https://mercyv.tebex.io', -- Tebex Store URL (wird geoeffnet wenn man auf Coins klickt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ local function HideHint()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function Notify(msg, type)
|
local function Notify(msg, type)
|
||||||
exports['hex_4_hud']:SendNotification(msg, type or "info")
|
exports['hex_4_hud']:Notify("Fahrrad", msg, type or "info", 3000)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- ── NPC spawnen ────────────────────────────────────────────────
|
-- ── NPC spawnen ────────────────────────────────────────────────
|
||||||
@ -70,6 +70,14 @@ end
|
|||||||
|
|
||||||
RegisterNetEvent('mercyv-bike:syncNPC', function(data)
|
RegisterNetEvent('mercyv-bike:syncNPC', function(data)
|
||||||
NPCData = data
|
NPCData = data
|
||||||
|
if not data then
|
||||||
|
-- NPC löschen
|
||||||
|
if NPCEntity and DoesEntityExist(NPCEntity) then
|
||||||
|
DeleteEntity(NPCEntity)
|
||||||
|
NPCEntity = nil
|
||||||
|
end
|
||||||
|
return
|
||||||
|
end
|
||||||
SpawnNPC(data)
|
SpawnNPC(data)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -136,33 +144,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 +223,47 @@ end)
|
|||||||
|
|
||||||
-- ── Fahrrad spawnen ────────────────────────────────────────────
|
-- ── Fahrrad spawnen ────────────────────────────────────────────
|
||||||
|
|
||||||
RegisterNetEvent('mercyv-bike:doSpawn', function(bikeModel)
|
RegisterNetEvent('mercyv-bike:doSpawn', function(bikeModel, spawnData, plate)
|
||||||
|
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)
|
||||||
@ -247,13 +272,17 @@ RegisterNetEvent('mercyv-bike:doSpawn', function(bikeModel)
|
|||||||
if DoesEntityExist(bike) then
|
if DoesEntityExist(bike) then
|
||||||
SetEntityAsMissionEntity(bike, true, true)
|
SetEntityAsMissionEntity(bike, true, true)
|
||||||
SetVehicleEngineOn(bike, true, true, false)
|
SetVehicleEngineOn(bike, true, true, false)
|
||||||
SetModelAsNoLongerNeeded(model)
|
-- Kennzeichen setzen falls vom Server übergeben
|
||||||
-- Schlüssel geben
|
if plate and plate ~= '' then
|
||||||
local ks = Config.VehicleKeySystem
|
SetVehicleNumberPlateText(bike, plate)
|
||||||
if ks == 'jaksam' then
|
|
||||||
TriggerServerEvent('vehicles_keys:selfGiveVehicleKeys',
|
|
||||||
GetVehicleNumberPlateText(bike))
|
|
||||||
end
|
end
|
||||||
|
SetModelAsNoLongerNeeded(model)
|
||||||
|
local finalPlate = plate or GetVehicleNumberPlateText(bike)
|
||||||
|
TriggerServerEvent('vehicles_keys:selfGiveVehicleKeys', finalPlate)
|
||||||
|
Notify("Fahrrad erhalten! Viel Spaß!", "success")
|
||||||
|
print('[mercyv-bike] Fahrrad gespawnt mit Kennzeichen: ' .. tostring(finalPlate))
|
||||||
|
else
|
||||||
|
Notify("Fahrrad konnte nicht gespawnt werden.", "error")
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
@ -268,3 +297,8 @@ RegisterCommand('bikeadmin', function()
|
|||||||
Citizen.Wait(200)
|
Citizen.Wait(200)
|
||||||
SendNUIMessage({ action = "OPEN_ADMIN", isAdmin = true })
|
SendNUIMessage({ action = "OPEN_ADMIN", isAdmin = true })
|
||||||
end, false)
|
end, false)
|
||||||
|
|
||||||
|
RegisterNUICallback('deleteNPC', function(data, cb)
|
||||||
|
TriggerServerEvent('mercyv-bike:deleteNPC')
|
||||||
|
cb({})
|
||||||
|
end)
|
||||||
|
|||||||
@ -20,9 +20,7 @@ Config.NPC = {
|
|||||||
|
|
||||||
-- Fahrrad-Optionen
|
-- Fahrrad-Optionen
|
||||||
Config.Bikes = {
|
Config.Bikes = {
|
||||||
{ model = "tribike", label = "Mountainbike", image = "tribike" },
|
{ model = "scorcher", label = "Scorcher", image = "scorcher" },
|
||||||
{ model = "tribike2", label = "Mountainbike Pro", image = "tribike2" },
|
|
||||||
{ model = "tribike3", label = "Mountainbike Max", image = "tribike3" },
|
|
||||||
}
|
}
|
||||||
|
|
||||||
-- Spawn-Position des Fahrrads (vor dem Spieler)
|
-- Spawn-Position des Fahrrads (vor dem Spieler)
|
||||||
@ -40,10 +38,10 @@ Config.Notify = {
|
|||||||
|
|
||||||
-- ─── Interne Hilfsfunktionen ────────────────────────────────────
|
-- ─── Interne Hilfsfunktionen ────────────────────────────────────
|
||||||
|
|
||||||
function Config.ClientNotification(msg, type)
|
function Config.ClientNotification(msg, ntype)
|
||||||
exports['hex_4_hud']:SendNotification(msg, type or "info")
|
exports['hex_4_hud']:Notify("Fahrrad", msg, ntype or "info", 3000)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Config.ServerNotification(src, msg, type)
|
function Config.ServerNotification(src, msg, ntype)
|
||||||
TriggerClientEvent('mercyv-bike:notify', src, msg, type or "info")
|
TriggerClientEvent("hex_4_hud:Notify", src, "Fahrrad", msg, ntype or "info", 3000)
|
||||||
end
|
end
|
||||||
|
|||||||
BIN
mercyv-bike/nui/images/scorcher.png
Normal file
BIN
mercyv-bike/nui/images/scorcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
File diff suppressed because one or more lines are too long
@ -2,15 +2,12 @@
|
|||||||
// MercyV Bike – NUI Script
|
// MercyV Bike – NUI Script
|
||||||
// ═══════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════
|
||||||
|
|
||||||
const { createApp } = Vue;
|
|
||||||
|
|
||||||
function postNUI(event, data) {
|
function postNUI(event, data) {
|
||||||
return $.post(`https://${GetParentResourceName()}/${event}`, JSON.stringify(data || {}));
|
return $.post(`https://${GetParentResourceName()}/${event}`, JSON.stringify(data || {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = createApp({
|
const app = new Vue({
|
||||||
data() {
|
data: {
|
||||||
return {
|
|
||||||
show: false, // WICHTIG: immer false beim Start
|
show: false, // WICHTIG: immer false beim Start
|
||||||
showAdmin: false,
|
showAdmin: false,
|
||||||
isAdmin: false,
|
isAdmin: false,
|
||||||
@ -67,7 +64,8 @@ const app = createApp({
|
|||||||
postNUI('saveNPC', { ...this.npc });
|
postNUI('saveNPC', { ...this.npc });
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}).mount('#app');
|
el: '#app'
|
||||||
|
});
|
||||||
|
|
||||||
// ── Message Handler ────────────────────────────────
|
// ── Message Handler ────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
98
mercyv-bike/nui/script_clean.js
Normal file
98
mercyv-bike/nui/script_clean.js
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
function postNUI(event, data) {
|
||||||
|
$.post('https://' + GetParentResourceName() + '/' + event, JSON.stringify(data || {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = new Vue({
|
||||||
|
el: '#app',
|
||||||
|
data: {
|
||||||
|
show: false,
|
||||||
|
showAdmin: false,
|
||||||
|
isAdmin: false,
|
||||||
|
bikes: [],
|
||||||
|
selected: null,
|
||||||
|
claimed: false,
|
||||||
|
claimedModel: null,
|
||||||
|
npc: {
|
||||||
|
model: 'a_m_m_beach_01',
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0,
|
||||||
|
heading: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close: function() {
|
||||||
|
this.show = false;
|
||||||
|
this.showAdmin = false;
|
||||||
|
this.selected = null;
|
||||||
|
postNUI('close');
|
||||||
|
},
|
||||||
|
claimBike: function() {
|
||||||
|
if (!this.selected) return;
|
||||||
|
postNUI('claimBike', { model: this.selected });
|
||||||
|
},
|
||||||
|
selectBike: function(model) {
|
||||||
|
this.selected = this.selected === model ? null : model;
|
||||||
|
},
|
||||||
|
openAdmin: function() {
|
||||||
|
this.showAdmin = true;
|
||||||
|
},
|
||||||
|
closeAdmin: function() {
|
||||||
|
this.showAdmin = false;
|
||||||
|
postNUI('close');
|
||||||
|
},
|
||||||
|
capturePos: function() {
|
||||||
|
postNUI('capturePos', {});
|
||||||
|
},
|
||||||
|
saveNPC: function() {
|
||||||
|
postNUI('saveNPC', {
|
||||||
|
model: this.npc.model,
|
||||||
|
x: this.npc.x,
|
||||||
|
y: this.npc.y,
|
||||||
|
z: this.npc.z,
|
||||||
|
heading: this.npc.heading
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('message', function(event) {
|
||||||
|
var msg = event.data;
|
||||||
|
switch (msg.action) {
|
||||||
|
case 'OPEN':
|
||||||
|
app.show = true;
|
||||||
|
app.showAdmin = false;
|
||||||
|
app.bikes = msg.bikes || [];
|
||||||
|
app.isAdmin = msg.isAdmin || false;
|
||||||
|
app.selected = null;
|
||||||
|
break;
|
||||||
|
case 'OPEN_ADMIN':
|
||||||
|
app.show = true;
|
||||||
|
app.showAdmin = true;
|
||||||
|
app.isAdmin = true;
|
||||||
|
if (msg.captured) {
|
||||||
|
app.npc.x = Math.round(msg.captured.x * 100) / 100;
|
||||||
|
app.npc.y = Math.round(msg.captured.y * 100) / 100;
|
||||||
|
app.npc.z = Math.round(msg.captured.z * 100) / 100;
|
||||||
|
app.npc.heading = Math.round(msg.captured.heading * 100) / 100;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'CLOSE':
|
||||||
|
app.show = false;
|
||||||
|
app.showAdmin = false;
|
||||||
|
break;
|
||||||
|
case 'SET_ADMIN':
|
||||||
|
app.isAdmin = msg.isAdmin;
|
||||||
|
break;
|
||||||
|
case 'SET_CLAIM_STATUS':
|
||||||
|
app.claimed = msg.claimed;
|
||||||
|
app.claimedModel = msg.model || null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', function(e) {
|
||||||
|
if (e.key === 'Escape' && app.show) {
|
||||||
|
app.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
@ -29,8 +29,6 @@ body {
|
|||||||
.mb-backdrop {
|
.mb-backdrop {
|
||||||
position: fixed; inset: 0;
|
position: fixed; inset: 0;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
background: rgba(0,0,0,0.55);
|
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
}
|
}
|
||||||
.mb-backdrop-transparent {
|
.mb-backdrop-transparent {
|
||||||
@ -41,7 +39,7 @@ body {
|
|||||||
|
|
||||||
/* ── Modal ─────────────────────────────────────── */
|
/* ── Modal ─────────────────────────────────────── */
|
||||||
.mb-modal {
|
.mb-modal {
|
||||||
width: 680px;
|
width: 820px;
|
||||||
background: var(--bg-modal);
|
background: var(--bg-modal);
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
border: 1px solid rgba(255,255,255,0.1);
|
border: 1px solid rgba(255,255,255,0.1);
|
||||||
@ -66,7 +64,7 @@ body {
|
|||||||
background: rgba(232,131,10,0.15);
|
background: rgba(232,131,10,0.15);
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
}
|
}
|
||||||
.mb-title-main { font-size: 16px; font-family: inherit; font-weight: 700; display: block; }
|
.mb-title-main { font-size: 18px; font-family: inherit; font-weight: 700; display: block; }
|
||||||
.mb-title-sub { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.4px; display: block; }
|
.mb-title-sub { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.4px; display: block; }
|
||||||
|
|
||||||
.mb-close-btn {
|
.mb-close-btn {
|
||||||
@ -99,7 +97,7 @@ body {
|
|||||||
display: flex; gap: 14px; justify-content: center; flex-wrap: wrap;
|
display: flex; gap: 14px; justify-content: center; flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
.mb-bike-card {
|
.mb-bike-card {
|
||||||
width: 160px; padding: 16px 12px;
|
width: 210px; padding: 20px 16px;
|
||||||
background: var(--bg-card);
|
background: var(--bg-card);
|
||||||
border: 2px solid var(--border);
|
border: 2px solid var(--border);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@ -109,14 +107,14 @@ body {
|
|||||||
}
|
}
|
||||||
.mb-bike-card:hover { border-color: rgba(232,131,10,0.4); transform: translateY(-2px); }
|
.mb-bike-card:hover { border-color: rgba(232,131,10,0.4); transform: translateY(-2px); }
|
||||||
.mb-bike-active { border-color: var(--accent) !important; background: rgba(232,131,10,0.08) !important; }
|
.mb-bike-active { border-color: var(--accent) !important; background: rgba(232,131,10,0.08) !important; }
|
||||||
.mb-bike-img { width: 120px; height: 75px; object-fit: contain; }
|
.mb-bike-img { width: 160px; height: 100px; object-fit: contain; }
|
||||||
.mb-bike-name { font-size: 13px; font-family: inherit; font-weight: 700; text-align: center; }
|
.mb-bike-name { font-size: 13px; font-family: inherit; font-weight: 700; text-align: center; }
|
||||||
.mb-bike-free { font-size: 11px; color: var(--accent); }
|
.mb-bike-free { font-size: 13px; color: var(--accent); }
|
||||||
.mb-bike-free i { margin-right: 4px; }
|
.mb-bike-free i { margin-right: 4px; }
|
||||||
|
|
||||||
/* ── Claim-Button ──────────────────────────────── */
|
/* ── Claim-Button ──────────────────────────────── */
|
||||||
.mb-claim-btn {
|
.mb-claim-btn {
|
||||||
padding: 13px 32px;
|
padding: 15px 40px;
|
||||||
background: var(--accent); border: none; border-radius: 8px;
|
background: var(--accent); border: none; border-radius: 8px;
|
||||||
color: #fff; font-size: 15px; font-family: inherit; font-weight: 700;
|
color: #fff; font-size: 15px; font-family: inherit; font-weight: 700;
|
||||||
cursor: pointer; letter-spacing: 0.04em;
|
cursor: pointer; letter-spacing: 0.04em;
|
||||||
|
|||||||
@ -15,6 +15,21 @@ local function GetIdentifier(src)
|
|||||||
return xp and xp.identifier or nil
|
return xp and xp.identifier or nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Fahrzeug-Tabelle (ESX)
|
||||||
|
local function vT() return "owned_vehicles" end
|
||||||
|
local function oC() return "owner" end
|
||||||
|
local function pC() return "vehicle" end
|
||||||
|
|
||||||
|
-- Zufälliges Kennzeichen generieren
|
||||||
|
local function GeneratePlate()
|
||||||
|
local chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ'
|
||||||
|
local plate = ''
|
||||||
|
for i = 1, 3 do plate = plate .. chars:sub(math.random(1,#chars),math.random(1,#chars)):sub(1,1) end
|
||||||
|
plate = plate .. ' '
|
||||||
|
for i = 1, 3 do plate = plate .. tostring(math.random(0,9)) end
|
||||||
|
return plate
|
||||||
|
end
|
||||||
|
|
||||||
local function IsAdmin(src)
|
local function IsAdmin(src)
|
||||||
if IsPlayerAceAllowed(src, Config.AdminAce) then return true end
|
if IsPlayerAceAllowed(src, Config.AdminAce) then return true end
|
||||||
if ESX then
|
if ESX then
|
||||||
@ -45,15 +60,44 @@ 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
|
||||||
Wait(1500)
|
Wait(1500)
|
||||||
-- NPC-Position aus DB laden falls gespeichert
|
-- 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
|
||||||
|
]])
|
||||||
|
|
||||||
|
-- Migration: Spalten hinzufügen falls noch nicht vorhanden
|
||||||
|
for _, col in ipairs({'spawn_x','spawn_y','spawn_z','spawn_heading'}) do
|
||||||
|
local exists = MySQL.query.await(string.format(
|
||||||
|
"SELECT COUNT(*) AS cnt FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='mercyv_bike_npc' AND COLUMN_NAME='%s'", col))
|
||||||
|
if exists and exists[1] and exists[1].cnt == 0 then
|
||||||
|
MySQL.query.await(string.format("ALTER TABLE `mercyv_bike_npc` ADD COLUMN `%s` FLOAT DEFAULT 0", col))
|
||||||
|
print('[mercyv-bike] Spalte ' .. col .. ' hinzugefügt.')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
-- 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,
|
||||||
@ -63,19 +107,7 @@ AddEventHandler('onResourceStart', function(res)
|
|||||||
heading = Config.NPC.heading,
|
heading = Config.NPC.heading,
|
||||||
}
|
}
|
||||||
end
|
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
|
|
||||||
]])
|
|
||||||
print('^2[mercyv-bike]^0 NPC-Daten geladen.')
|
print('^2[mercyv-bike]^0 NPC-Daten geladen.')
|
||||||
-- Kein Broadcast hier - jeder Client bekommt die Daten beim clientReady
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- ── Client ready ───────────────────────────────────────────────
|
-- ── Client ready ───────────────────────────────────────────────
|
||||||
@ -111,16 +143,30 @@ RegisterNetEvent('mercyv-bike:claim', function(bikeModel)
|
|||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Eintragen
|
-- Zufälliges Kennzeichen
|
||||||
|
local plate = GeneratePlate()
|
||||||
|
|
||||||
|
-- Als eigenes Fahrzeug in owned_vehicles eintragen
|
||||||
|
local tbl = vT(); local owner = oC(); local props = pC()
|
||||||
|
local modelHash = joaat and joaat(bikeModel) or 0
|
||||||
|
local skinData = json.encode({ model = GetHashKey(bikeModel), plate = plate })
|
||||||
|
|
||||||
|
-- Fahrzeug eintragen mit stored=0 (ist direkt in der Welt)
|
||||||
|
MySQL.insert(
|
||||||
|
string.format("INSERT INTO `%s` (plate, `%s`, `%s`, stored, parking, job, veh_class) VALUES (?, ?, ?, 0, 'Garage A', '', 13)", tbl, owner, props),
|
||||||
|
{ plate, identifier, skinData }
|
||||||
|
)
|
||||||
|
|
||||||
|
-- Claim eintragen
|
||||||
MySQL.insert(
|
MySQL.insert(
|
||||||
'INSERT INTO mercyv_bike_claims (identifier, bike_model) VALUES (?, ?)',
|
'INSERT INTO mercyv_bike_claims (identifier, bike_model) VALUES (?, ?)',
|
||||||
{ identifier, bikeModel }
|
{ identifier, bikeModel }
|
||||||
)
|
)
|
||||||
|
|
||||||
-- Spawn-Signal
|
-- Spawn-Signal mit Kennzeichen
|
||||||
TriggerClientEvent('mercyv-bike:doSpawn', src, bikeModel)
|
TriggerClientEvent('mercyv-bike:doSpawn', src, bikeModel, SpawnData, plate)
|
||||||
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 (Kennzeichen: %s).', identifier, bikeModel, plate))
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- ── Prüfen ob Spieler bereits eines hat ───────────────────────
|
-- ── Prüfen ob Spieler bereits eines hat ───────────────────────
|
||||||
@ -149,15 +195,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)
|
||||||
@ -185,3 +238,13 @@ RegisterCommand('bikereset', function(src, args)
|
|||||||
Config.ServerNotification(src, 'Claim von Spieler ' .. targetId .. ' zurückgesetzt.', 'success')
|
Config.ServerNotification(src, 'Claim von Spieler ' .. targetId .. ' zurückgesetzt.', 'success')
|
||||||
Config.ServerNotification(targetId, 'Dein Fahrrad-Claim wurde zurückgesetzt.', 'info')
|
Config.ServerNotification(targetId, 'Dein Fahrrad-Claim wurde zurückgesetzt.', 'info')
|
||||||
end, false)
|
end, false)
|
||||||
|
|
||||||
|
RegisterNetEvent('mercyv-bike:deleteNPC', function()
|
||||||
|
local src = source
|
||||||
|
if not IsAdmin(src) then return end
|
||||||
|
MySQL.update('DELETE FROM mercyv_bike_npc')
|
||||||
|
NPCData = nil
|
||||||
|
SpawnData = nil
|
||||||
|
TriggerClientEvent('mercyv-bike:syncNPC', -1, nil)
|
||||||
|
Config.ServerNotification(src, "NPC gelöscht.", "success")
|
||||||
|
end)
|
||||||
|
|||||||
@ -692,5 +692,38 @@ CreateThread(function()
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
}) -- Diese Zeile schließt den ox_target Block
|
})
|
||||||
end) -- Diese Zeile schließt den CreateThread
|
end)
|
||||||
|
|
||||||
|
-- ============================================================
|
||||||
|
-- NPC REVIVE TELEPORT & ANIMATION (Für den Wiederbelebten)
|
||||||
|
-- ============================================================
|
||||||
|
RegisterNetEvent('mercyv-deathscreen:client:npcReviveTeleport')
|
||||||
|
AddEventHandler('mercyv-deathscreen:client:npcReviveTeleport', function()
|
||||||
|
local ped = PlayerPedId()
|
||||||
|
|
||||||
|
-- Kurz warten, damit der Spieler nach dem Revive sicher am Leben ist
|
||||||
|
Wait(200)
|
||||||
|
|
||||||
|
-- Bildschirm langsam schwarz ausblenden (Dauer: 1000 Millisekunden = 1 Sekunde)
|
||||||
|
DoScreenFadeOut(1000)
|
||||||
|
|
||||||
|
-- Warten, bis der Bildschirm komplett schwarz ist
|
||||||
|
Wait(1000)
|
||||||
|
|
||||||
|
-- Spieler zum Bett teleportieren (während der Bildschirm schwarz ist)
|
||||||
|
SetEntityCoords(ped, -345.2544, -602.0786, 38.1887, false, false, false, true)
|
||||||
|
SetEntityHeading(ped, 297.6853)
|
||||||
|
|
||||||
|
-- Dem Server kurz Zeit geben, die Position zu synchronisieren
|
||||||
|
Wait(500)
|
||||||
|
|
||||||
|
-- Emote-Menü Befehl ausführen (Spieler legt sich hin)
|
||||||
|
ExecuteCommand('e sleep')
|
||||||
|
|
||||||
|
-- Noch kurz warten, damit die Animation schon gestartet ist, wenn das Bild wiederkommt
|
||||||
|
Wait(1000)
|
||||||
|
|
||||||
|
-- Bildschirm wieder sanft einblenden (1000 ms)
|
||||||
|
DoScreenFadeIn(1000)
|
||||||
|
end)
|
||||||
@ -374,6 +374,7 @@ AddEventHandler('mercyv-deathscreen:server:npcHealSelf', function()
|
|||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
-- NPC Wiederbelebung für andere
|
||||||
-- NPC Wiederbelebung für andere
|
-- NPC Wiederbelebung für andere
|
||||||
RegisterNetEvent('mercyv-deathscreen:server:npcReviveOther')
|
RegisterNetEvent('mercyv-deathscreen:server:npcReviveOther')
|
||||||
AddEventHandler('mercyv-deathscreen:server:npcReviveOther', function(targetId)
|
AddEventHandler('mercyv-deathscreen:server:npcReviveOther', function(targetId)
|
||||||
@ -401,6 +402,9 @@ AddEventHandler('mercyv-deathscreen:server:npcReviveOther', function(targetId)
|
|||||||
if RevivePlayer(targetId) then
|
if RevivePlayer(targetId) then
|
||||||
TriggerClientEvent('esx:showNotification', src, 'Du hast die Person für $' .. cost .. ' wiederbelebt.')
|
TriggerClientEvent('esx:showNotification', src, 'Du hast die Person für $' .. cost .. ' wiederbelebt.')
|
||||||
TriggerClientEvent('esx:showNotification', targetId, 'Du wurdest von jemandem beim NPC-Arzt gerettet.')
|
TriggerClientEvent('esx:showNotification', targetId, 'Du wurdest von jemandem beim NPC-Arzt gerettet.')
|
||||||
|
|
||||||
|
-- HIER IST NEU: Teleportiert den ZIEL-SPIELER (targetId) ins Bett und startet /e sleep
|
||||||
|
TriggerClientEvent('mercyv-deathscreen:client:npcReviveTeleport', targetId)
|
||||||
else
|
else
|
||||||
-- Falls die Wiederbelebung fehlschlägt, geben wir das Geld zurück
|
-- Falls die Wiederbelebung fehlschlägt, geben wir das Geld zurück
|
||||||
xPlayer.addMoney(cost)
|
xPlayer.addMoney(cost)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user