Auto-sync 2026-04-15 22:20

This commit is contained in:
root 2026-04-15 22:20:01 +02:00
parent 9b30857065
commit 4d7f4efcea
7 changed files with 68 additions and 24 deletions

View File

@ -2,6 +2,13 @@
-- MercyV Bike Client -- MercyV Bike Client
-- ═══════════════════════════════════════════════════════════════ -- ═══════════════════════════════════════════════════════════════
-- NUI beim Start sicher versteckt halten
AddEventHandler('onClientResourceStart', function(res)
if res ~= GetCurrentResourceName() then return end
SetNuiFocus(false, false)
SendNUIMessage({ action = "CLOSE" })
end)
local NPCEntity = nil local NPCEntity = nil
local NPCData = nil local NPCData = nil
local IsAdmin = false local IsAdmin = false

View File

@ -4,13 +4,13 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>MercyV Bike</title> <title>MercyV Bike</title>
<link rel="stylesheet" href="style.css"> <link rel="stylesheet" href="style.css">
<script src="vue.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.7.14/vue.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head> </head>
<body> <body>
<div id="app" v-show="show"> <div id="app" v-show="show" style="display:none;">
<!-- ══════════════════ HAUPT-PANEL ══════════════════ --> <!-- ══════════════════ HAUPT-PANEL ══════════════════ -->
<div class="mb-backdrop" v-show="!showAdmin"> <div class="mb-backdrop" v-show="!showAdmin">

View File

@ -11,7 +11,7 @@ function postNUI(event, data) {
const app = createApp({ const app = createApp({
data() { data() {
return { return {
show: false, show: false, // WICHTIG: immer false beim Start
showAdmin: false, showAdmin: false,
isAdmin: false, isAdmin: false,
bikes: [], bikes: [],
@ -28,6 +28,11 @@ const app = createApp({
}; };
}, },
mounted() {
// Vue ist gemountet - div kann jetzt gesteuert werden
document.getElementById('app').style.display = '';
},
methods: { methods: {
close() { close() {
this.show = false; this.show = false;

View File

@ -2,11 +2,6 @@
MercyV Bike NUI Style (gleiche Sprache wie mercyv-garage) MercyV Bike NUI Style (gleiche Sprache wie mercyv-garage)
*/ */
@font-face {
font-family: "GilroyBold";
src: url("../mercyv-garage/nui/fonts/Gilroy-Bold.ttf") format("truetype");
}
:root { :root {
--accent: #E8830A; --accent: #E8830A;
--accent-hover: #F59D2A; --accent-hover: #F59D2A;
@ -22,7 +17,7 @@
* { box-sizing: border-box; margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; }
body { body {
font-family: "GilroyBold", -apple-system, sans-serif; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; font-weight: 700;
background: transparent; background: transparent;
color: var(--text-primary); color: var(--text-primary);
user-select: none; user-select: none;
@ -71,7 +66,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: "GilroyBold", sans-serif; display: block; } .mb-title-main { font-size: 16px; 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 {
@ -115,7 +110,7 @@ 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: 120px; height: 75px; object-fit: contain; }
.mb-bike-name { font-size: 13px; font-family: "GilroyBold", sans-serif; 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: 11px; color: var(--accent); }
.mb-bike-free i { margin-right: 4px; } .mb-bike-free i { margin-right: 4px; }
@ -123,7 +118,7 @@ body {
.mb-claim-btn { .mb-claim-btn {
padding: 13px 32px; padding: 13px 32px;
background: var(--accent); border: none; border-radius: 8px; background: var(--accent); border: none; border-radius: 8px;
color: #fff; font-size: 15px; font-family: "GilroyBold", sans-serif; color: #fff; font-size: 15px; font-family: inherit; font-weight: 700;
cursor: pointer; letter-spacing: 0.04em; cursor: pointer; letter-spacing: 0.04em;
transition: background 0.15s, transform 0.1s; transition: background 0.15s, transform 0.1s;
} }
@ -172,7 +167,7 @@ body {
.mb-save-btn { .mb-save-btn {
padding: 12px; border-radius: 8px; padding: 12px; border-radius: 8px;
background: var(--accent); border: none; background: var(--accent); border: none;
color: #fff; font-size: 14px; font-family: "GilroyBold", sans-serif; color: #fff; font-size: 14px; font-family: inherit; font-weight: 700;
cursor: pointer; letter-spacing: 0.04em; cursor: pointer; letter-spacing: 0.04em;
transition: background 0.15s, transform 0.1s; transition: background 0.15s, transform 0.1s;
} }

View File

@ -74,8 +74,8 @@ AddEventHandler('onResourceStart', function(res)
`heading` FLOAT DEFAULT 0 `heading` FLOAT DEFAULT 0
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
]]) ]])
TriggerClientEvent('mercyv-bike:syncNPC', -1, NPCData)
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 ───────────────────────────────────────────────

View File

@ -642,13 +642,13 @@ CreateThread(function()
SetBlockingOfNonTemporaryEvents(npcEntity, true) SetBlockingOfNonTemporaryEvents(npcEntity, true)
-- ox_target Optionen für den NPC -- ox_target Optionen für den NPC
-- ox_target Optionen für den NPC
exports.ox_target:addLocalEntity(npcEntity, { exports.ox_target:addLocalEntity(npcEntity, {
{ {
name = 'medic_npc_heal', name = 'medic_npc_heal',
icon = 'fas fa-kit-medical', icon = 'fas fa-kit-medical',
label = 'Sich selbst heilen (Kostenlos)', label = 'Sich selbst heilen ($1000)', -- Text angepasst
canInteract = function() canInteract = function()
-- Wird nur angezeigt, wenn man lebt und nicht volle HP (200) hat
return deathState == 'ALIVE' and GetEntityHealth(PlayerPedId()) < 200 return deathState == 'ALIVE' and GetEntityHealth(PlayerPedId()) < 200
end, end,
onSelect = function() onSelect = function()
@ -658,9 +658,8 @@ CreateThread(function()
{ {
name = 'medic_npc_revive', name = 'medic_npc_revive',
icon = 'fas fa-heart-pulse', icon = 'fas fa-heart-pulse',
label = 'Person in der Nähe wiederbeleben', label = 'Person in der Nähe wiederbeleben ($1500)', -- Text angepasst
canInteract = function() canInteract = function()
-- Prüfen ob jemand am Boden liegt in der Nähe des Spielers
local coords = GetEntityCoords(PlayerPedId()) local coords = GetEntityCoords(PlayerPedId())
local players = ESX.Game.GetPlayersInArea(coords, 5.0) local players = ESX.Game.GetPlayersInArea(coords, 5.0)
@ -692,5 +691,4 @@ CreateThread(function()
end end
end end
} }
}) }
end)

View File

@ -333,6 +333,12 @@ end)
-- NPC MEDICAL SERVER LOGIC -- NPC MEDICAL SERVER LOGIC
-- ============================================================ -- ============================================================
-- Preise für den NPC-Arzt einstellen
local NPCCosts = {
Heal = 1000, -- Kosten für "Sich selbst heilen"
Revive = 1500 -- Kosten für "Andere Person wiederbeleben"
}
-- Hilfsfunktion: Prüft ob EMS im Dienst ist -- Hilfsfunktion: Prüft ob EMS im Dienst ist
local function IsEMSOnDuty() local function IsEMSOnDuty()
local xPlayers = ESX.GetExtendedPlayers('job', 'ambulance') -- Ersetze 'ambulance' falls dein Job anders heißt local xPlayers = ESX.GetExtendedPlayers('job', 'ambulance') -- Ersetze 'ambulance' falls dein Job anders heißt
@ -343,6 +349,7 @@ end
RegisterNetEvent('mercyv-deathscreen:server:npcHealSelf') RegisterNetEvent('mercyv-deathscreen:server:npcHealSelf')
AddEventHandler('mercyv-deathscreen:server:npcHealSelf', function() AddEventHandler('mercyv-deathscreen:server:npcHealSelf', function()
local src = source local src = source
if IsEMSOnDuty() then if IsEMSOnDuty() then
TriggerClientEvent('esx:showNotification', src, 'Es sind Sanitäter im Dienst! Bitte rufe den Notruf.') TriggerClientEvent('esx:showNotification', src, 'Es sind Sanitäter im Dienst! Bitte rufe den Notruf.')
return return
@ -350,8 +357,20 @@ AddEventHandler('mercyv-deathscreen:server:npcHealSelf', function()
local xPlayer = ESX.GetPlayerFromId(src) local xPlayer = ESX.GetPlayerFromId(src)
if xPlayer then if xPlayer then
local cost = NPCCosts.Heal
-- Bezahl-Logik (zuerst Bargeld, dann Bank)
if xPlayer.getMoney() >= cost then
xPlayer.removeMoney(cost)
elseif xPlayer.getAccount('bank').money >= cost then
xPlayer.removeAccountMoney('bank', cost)
else
TriggerClientEvent('esx:showNotification', src, 'Du hast nicht genug Geld! (Kosten: $' .. cost .. ')')
return -- Bricht hier ab, wenn kein Geld da ist
end
TriggerClientEvent('mercyv-deathscreen:client:healed', src, 200, 'big') TriggerClientEvent('mercyv-deathscreen:client:healed', src, 200, 'big')
TriggerClientEvent('esx:showNotification', src, 'Der Arzt hat deine Wunden versorgt.') TriggerClientEvent('esx:showNotification', src, 'Der Arzt hat deine Wunden für $' .. cost .. ' versorgt.')
end end
end) end)
@ -359,13 +378,33 @@ end)
RegisterNetEvent('mercyv-deathscreen:server:npcReviveOther') RegisterNetEvent('mercyv-deathscreen:server:npcReviveOther')
AddEventHandler('mercyv-deathscreen:server:npcReviveOther', function(targetId) AddEventHandler('mercyv-deathscreen:server:npcReviveOther', function(targetId)
local src = source local src = source
if IsEMSOnDuty() then if IsEMSOnDuty() then
TriggerClientEvent('esx:showNotification', src, 'Es sind Sanitäter im Dienst! Bitte rufe den Notruf.') TriggerClientEvent('esx:showNotification', src, 'Es sind Sanitäter im Dienst! Bitte rufe den Notruf.')
return return
end end
if RevivePlayer(targetId) then local xPlayer = ESX.GetPlayerFromId(src)
TriggerClientEvent('esx:showNotification', src, 'Du hast die Person mit Hilfe des Arztes wiederbelebt.') if xPlayer then
TriggerClientEvent('esx:showNotification', targetId, 'Du wurdest von jemandem beim NPC-Arzt gerettet.') local cost = NPCCosts.Revive
-- Bezahl-Logik (zuerst Bargeld, dann Bank)
if xPlayer.getMoney() >= cost then
xPlayer.removeMoney(cost)
elseif xPlayer.getAccount('bank').money >= cost then
xPlayer.removeAccountMoney('bank', cost)
else
TriggerClientEvent('esx:showNotification', src, 'Du hast nicht genug Geld! (Kosten: $' .. cost .. ')')
return -- Bricht hier ab, wenn kein Geld da ist
end
if RevivePlayer(targetId) then
TriggerClientEvent('esx:showNotification', src, 'Du hast die Person für $' .. cost .. ' wiederbelebt.')
TriggerClientEvent('esx:showNotification', targetId, 'Du wurdest von jemandem beim NPC-Arzt gerettet.')
else
-- Falls die Wiederbelebung fehlschlägt, geben wir das Geld zurück
xPlayer.addMoney(cost)
TriggerClientEvent('esx:showNotification', src, 'Fehler bei der Wiederbelebung. Geld wurde erstattet.')
end
end end
end) end)