2026-04-14 15:54:53 +02:00

778 lines
29 KiB
Lua

ESX = exports['es_extended']:getSharedObject()
-- In-memory caches
local treasury = {} -- faction_id -> { money, black_money }
local permissions = {} -- faction_id -> { perm_key -> min_grade }
local spawnedVehicles = {} -- source -> { vehicle_net_id, faction_id }
-- =====================
-- HELPERS
-- =====================
local function GetIdentifier(source)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return nil end
return xPlayer.getIdentifier()
end
local function GetPlayerName(source)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return 'Unbekannt' end
return xPlayer.getName()
end
local function GetPlayerFaction(source)
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return nil, nil end
local job = xPlayer.getJob()
if not job or not Config.Factions[job.name] then return nil, nil end
return job.name, job.grade
end
local function HasPermission(factionId, playerGrade, permissionKey)
local minGrade
if permissions[factionId] and permissions[factionId][permissionKey] ~= nil then
minGrade = permissions[factionId][permissionKey]
else
minGrade = Config.DefaultPermissions[permissionKey] or 99
end
return playerGrade >= minGrade
end
local function GetPermissionValue(factionId, permissionKey)
if permissions[factionId] and permissions[factionId][permissionKey] ~= nil then
return permissions[factionId][permissionKey]
end
return Config.DefaultPermissions[permissionKey] or 99
end
-- =====================
-- WEBHOOK
-- =====================
local function SendWebhook(title, message, color)
if not Config.WebhookURL or Config.WebhookURL == "" then return end
local embed = {
{
["color"] = color or 3447003,
["title"] = "**" .. title .. "**",
["description"] = message,
["footer"] = { ["text"] = os.date("%d.%m.%Y | %H:%M:%S") },
}
}
PerformHttpRequest(Config.WebhookURL, function() end, 'POST', json.encode({username = "Fraktion Log", embeds = embed}), { ['Content-Type'] = 'application/json' })
end
-- =====================
-- DATABASE SETUP
-- =====================
MySQL.ready(function()
MySQL.query.await([[CREATE TABLE IF NOT EXISTS mercyv_faction_treasury (
id INT AUTO_INCREMENT PRIMARY KEY,
faction_id VARCHAR(50) NOT NULL UNIQUE,
balance_money INT NOT NULL DEFAULT 0,
balance_black_money INT NOT NULL DEFAULT 0,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
)]])
MySQL.query.await([[CREATE TABLE IF NOT EXISTS mercyv_faction_transactions (
id INT AUTO_INCREMENT PRIMARY KEY,
faction_id VARCHAR(50) NOT NULL,
player_identifier VARCHAR(60) NOT NULL,
player_name VARCHAR(100) NOT NULL,
type ENUM('deposit', 'withdraw') NOT NULL,
account_type ENUM('money', 'black_money') NOT NULL,
amount INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_faction (faction_id),
INDEX idx_created (created_at)
)]])
MySQL.query.await([[CREATE TABLE IF NOT EXISTS mercyv_faction_permissions (
id INT AUTO_INCREMENT PRIMARY KEY,
faction_id VARCHAR(50) NOT NULL,
permission_key VARCHAR(50) NOT NULL,
min_grade INT NOT NULL DEFAULT 0,
UNIQUE KEY uq_faction_perm (faction_id, permission_key)
)]])
-- Ensure all config factions have treasury entries
for factionId, _ in pairs(Config.Factions) do
MySQL.query.await('INSERT IGNORE INTO mercyv_faction_treasury (faction_id) VALUES (?)', { factionId })
end
-- Load treasury
local tOk, treasuryRows = pcall(function()
return MySQL.query.await('SELECT * FROM mercyv_faction_treasury')
end)
if tOk and treasuryRows then
for _, row in ipairs(treasuryRows) do
if row.faction_id then
treasury[row.faction_id] = {
money = row.balance_money or 0,
black_money = row.balance_black_money or 0,
}
end
end
end
-- Load permissions
local permOk, permRows = pcall(function()
return MySQL.query.await('SELECT * FROM mercyv_faction_permissions')
end)
if permOk and permRows then
for _, row in ipairs(permRows) do
if row.faction_id and row.permission_key then
if not permissions[row.faction_id] then permissions[row.faction_id] = {} end
permissions[row.faction_id][row.permission_key] = row.min_grade
end
end
end
print('[mercyv-fraks] System gestartet (' .. #(treasuryRows or {}) .. ' Fraktionen geladen)')
end)
-- =====================
-- GET FACTION MEMBERS
-- =====================
local function GetFactionMembers(factionId)
local members = {}
local onlinePlayers = ESX.GetExtendedPlayers()
-- Online-Spieler mit passendem Job
local onlineIdentifiers = {}
for _, xPlayer in pairs(onlinePlayers) do
local job = xPlayer.getJob()
if job and job.name == factionId then
onlineIdentifiers[xPlayer.getIdentifier()] = true
members[#members + 1] = {
identifier = xPlayer.getIdentifier(),
name = xPlayer.getName(),
grade = job.grade,
grade_label = job.grade_label or ('Rang ' .. job.grade),
online = true,
source = xPlayer.source,
}
end
end
-- Offline-Spieler aus DB
local rows = MySQL.query.await('SELECT identifier, firstname, lastname, job_grade FROM users WHERE job = ?', { factionId })
if rows then
for _, row in ipairs(rows) do
if not onlineIdentifiers[row.identifier] then
members[#members + 1] = {
identifier = row.identifier,
name = (row.firstname or '') .. ' ' .. (row.lastname or ''),
grade = row.job_grade,
grade_label = 'Rang ' .. row.job_grade,
online = false,
source = nil,
}
end
end
end
return members
end
local function GetOnlineMemberCount(factionId)
local count = 0
for _, xPlayer in pairs(ESX.GetExtendedPlayers()) do
local job = xPlayer.getJob()
if job and job.name == factionId then count = count + 1 end
end
return count
end
local function GetTotalMemberCount(factionId)
local result = MySQL.query.await('SELECT COUNT(*) as cnt FROM users WHERE job = ?', { factionId })
if result and result[1] then return result[1].cnt end
return 0
end
-- =====================
-- GET JOB GRADES
-- =====================
local function GetJobGrades(factionId)
local rows = MySQL.query.await('SELECT grade, label FROM job_grades WHERE job_name = ? ORDER BY grade ASC', { factionId })
if not rows then return {} end
local grades = {}
for _, row in ipairs(rows) do
grades[#grades + 1] = { grade = row.grade, label = row.label }
end
return grades
end
local function GetLowestGrade(factionId)
local grades = GetJobGrades(factionId)
if #grades == 0 then return 0 end
return grades[1].grade
end
local function IsValidGrade(factionId, grade)
local grades = GetJobGrades(factionId)
for _, g in ipairs(grades) do
if g.grade == grade then return true end
end
return false
end
-- =====================
-- CALLBACKS
-- =====================
ESX.RegisterServerCallback('mercyv-fraks:getFactionData', function(source, cb)
local factionId, grade = GetPlayerFaction(source)
if not factionId then return cb(nil) end
local config = Config.Factions[factionId]
local bal = treasury[factionId] or { money = 0, black_money = 0 }
-- Build permissions for this player
local playerPerms = {}
for permKey, _ in pairs(Config.DefaultPermissions) do
playerPerms[permKey] = HasPermission(factionId, grade, permKey)
end
cb({
factionId = factionId,
factionLabel = config.label,
factionImage = config.image or '',
playerGrade = grade,
grades = GetJobGrades(factionId),
treasury = {
money = bal.money,
black_money = bal.black_money,
},
totalMembers = GetTotalMemberCount(factionId),
onlineMembers = GetOnlineMemberCount(factionId),
permissions = playerPerms,
permissionValues = GetAllPermissionValues(factionId),
theme = config.theme or {},
vehicles = config.vehicles or {},
})
end)
function GetAllPermissionValues(factionId)
local values = {}
for permKey, _ in pairs(Config.DefaultPermissions) do
values[permKey] = GetPermissionValue(factionId, permKey)
end
return values
end
ESX.RegisterServerCallback('mercyv-fraks:getMembers', function(source, cb)
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'viewMembers') then return cb({}) end
cb(GetFactionMembers(factionId))
end)
ESX.RegisterServerCallback('mercyv-fraks:getTreasuryLog', function(source, cb)
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'viewTreasury') then return cb({}) end
local rows = MySQL.query.await('SELECT * FROM mercyv_faction_transactions WHERE faction_id = ? ORDER BY created_at DESC LIMIT 50', { factionId })
cb(rows or {})
end)
ESX.RegisterServerCallback('mercyv-fraks:getNearbyPlayers', function(source, cb)
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'inviteMember') then return cb({}) end
local sourceCoords = GetEntityCoords(GetPlayerPed(source))
local nearby = {}
for _, xPlayer in pairs(ESX.GetExtendedPlayers()) do
if xPlayer.source ~= source then
local job = xPlayer.getJob()
if not job or job.name ~= factionId then
local targetCoords = GetEntityCoords(GetPlayerPed(xPlayer.source))
local dist = #(sourceCoords - targetCoords)
if dist <= Config.InviteRadius then
nearby[#nearby + 1] = {
source = xPlayer.source,
name = xPlayer.getName(),
job = job and job.label or 'Arbeitslos',
}
end
end
end
end
cb(nearby)
end)
-- =====================
-- INVITE SYSTEM
-- =====================
RegisterNetEvent('mercyv-fraks:inviteMember')
AddEventHandler('mercyv-fraks:inviteMember', function(targetSource)
local source = source
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'inviteMember') then return end
-- Check member limit
local config = Config.Factions[factionId]
local maxMembers = config.maxMembers or Config.MaxMembersDefault
if GetTotalMemberCount(factionId) >= maxMembers then
TriggerClientEvent('mercyv-fraks:notify', source, 'Mitgliederlimit erreicht (' .. maxMembers .. ')', 'error')
return
end
local targetPlayer = ESX.GetPlayerFromId(targetSource)
if not targetPlayer then return end
-- Send invite to target
TriggerClientEvent('mercyv-fraks:receiveInvite', targetSource, {
factionId = factionId,
factionLabel = config.label,
inviterName = GetPlayerName(source),
inviterSource = source,
})
TriggerClientEvent('mercyv-fraks:notify', source, 'Einladung gesendet an ' .. targetPlayer.getName(), 'info')
end)
RegisterNetEvent('mercyv-fraks:respondInvite')
AddEventHandler('mercyv-fraks:respondInvite', function(accepted, factionId, inviterSource)
local source = source
local xPlayer = ESX.GetPlayerFromId(source)
if not xPlayer then return end
if not accepted then
if inviterSource then
TriggerClientEvent('mercyv-fraks:notify', inviterSource, xPlayer.getName() .. ' hat die Einladung abgelehnt', 'error')
end
return
end
local config = Config.Factions[factionId]
if not config then return end
-- Set job to lowest available grade
local lowestGrade = GetLowestGrade(factionId)
xPlayer.setJob(factionId, lowestGrade)
TriggerClientEvent('mercyv-fraks:notify', source, 'Du bist ' .. config.label .. ' beigetreten!', 'success')
if inviterSource then
TriggerClientEvent('mercyv-fraks:notify', inviterSource, xPlayer.getName() .. ' ist beigetreten!', 'success')
end
SendWebhook("Mitglied Beigetreten", string.format(
"Fraktion: **%s**\nSpieler: **%s** (%s)\nEingeladen von: **%s**",
config.label, xPlayer.getName(), xPlayer.getIdentifier(),
inviterSource and GetPlayerName(inviterSource) or 'Unbekannt'
), 3066993)
end)
-- =====================
-- KICK MEMBER
-- =====================
RegisterNetEvent('mercyv-fraks:kickMember')
AddEventHandler('mercyv-fraks:kickMember', function(targetIdentifier)
local source = source
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'kickMember') then return end
local config = Config.Factions[factionId]
-- Find target (online or offline)
local targetOnline = nil
for _, xPlayer in pairs(ESX.GetExtendedPlayers()) do
if xPlayer.getIdentifier() == targetIdentifier then
targetOnline = xPlayer
break
end
end
local targetName = 'Unbekannt'
if targetOnline then
-- Check can't kick same or higher grade
local targetJob = targetOnline.getJob()
if targetJob and targetJob.grade >= grade then
TriggerClientEvent('mercyv-fraks:notify', source, 'Du kannst diesen Rang nicht entlassen', 'error')
return
end
targetName = targetOnline.getName()
targetOnline.setJob('unemployed', 0)
TriggerClientEvent('mercyv-fraks:notify', targetOnline.source, 'Du wurdest aus ' .. config.label .. ' entlassen', 'error')
else
-- Offline kick via DB
local rows = MySQL.query.await('SELECT firstname, lastname, job_grade FROM users WHERE identifier = ? AND job = ?', { targetIdentifier, factionId })
if not rows or #rows == 0 then
TriggerClientEvent('mercyv-fraks:notify', source, 'Spieler nicht gefunden', 'error')
return
end
if rows[1].job_grade >= grade then
TriggerClientEvent('mercyv-fraks:notify', source, 'Du kannst diesen Rang nicht entlassen', 'error')
return
end
targetName = (rows[1].firstname or '') .. ' ' .. (rows[1].lastname or '')
MySQL.query('UPDATE users SET job = ?, job_grade = ? WHERE identifier = ?', { 'unemployed', 0, targetIdentifier })
end
TriggerClientEvent('mercyv-fraks:notify', source, targetName .. ' wurde entlassen', 'success')
SendWebhook("Mitglied Entlassen", string.format(
"Fraktion: **%s**\nEntlassen: **%s** (%s)\nVon: **%s**",
config.label, targetName, targetIdentifier, GetPlayerName(source)
), 15158332)
end)
-- =====================
-- CHANGE RANK
-- =====================
RegisterNetEvent('mercyv-fraks:changeRank')
AddEventHandler('mercyv-fraks:changeRank', function(targetIdentifier, newGrade)
local source = source
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'changeRank') then return end
newGrade = math.max(0, math.floor(tonumber(newGrade) or 0))
local config = Config.Factions[factionId]
-- Can't set grade >= own grade
if newGrade >= grade then
TriggerClientEvent('mercyv-fraks:notify', source, 'Du kannst keinen Rang auf oder ueber deinem eigenen setzen', 'error')
return
end
-- Validate grade exists in job_grades
if not IsValidGrade(factionId, newGrade) then
TriggerClientEvent('mercyv-fraks:notify', source, 'Ungueltiger Rang', 'error')
return
end
local targetOnline = nil
for _, xPlayer in pairs(ESX.GetExtendedPlayers()) do
if xPlayer.getIdentifier() == targetIdentifier then
targetOnline = xPlayer
break
end
end
local targetName = 'Unbekannt'
if targetOnline then
local targetJob = targetOnline.getJob()
if targetJob and targetJob.grade >= grade then
TriggerClientEvent('mercyv-fraks:notify', source, 'Du kannst diesen Rang nicht aendern', 'error')
return
end
targetName = targetOnline.getName()
targetOnline.setJob(factionId, newGrade)
TriggerClientEvent('mercyv-fraks:notify', targetOnline.source, 'Dein Rang wurde geaendert', 'info')
else
local rows = MySQL.query.await('SELECT firstname, lastname, job_grade FROM users WHERE identifier = ? AND job = ?', { targetIdentifier, factionId })
if not rows or #rows == 0 then
TriggerClientEvent('mercyv-fraks:notify', source, 'Spieler nicht gefunden', 'error')
return
end
if rows[1].job_grade >= grade then
TriggerClientEvent('mercyv-fraks:notify', source, 'Du kannst diesen Rang nicht aendern', 'error')
return
end
targetName = (rows[1].firstname or '') .. ' ' .. (rows[1].lastname or '')
MySQL.query('UPDATE users SET job_grade = ? WHERE identifier = ? AND job = ?', { newGrade, targetIdentifier, factionId })
end
TriggerClientEvent('mercyv-fraks:notify', source, 'Rang von ' .. targetName .. ' geaendert', 'success')
SendWebhook("Rang Geaendert", string.format(
"Fraktion: **%s**\nSpieler: **%s**\nNeuer Rang: **%d**\nVon: **%s**",
config.label, targetName, newGrade, GetPlayerName(source)
), 3447003)
end)
-- =====================
-- TREASURY
-- =====================
RegisterNetEvent('mercyv-fraks:depositTreasury')
AddEventHandler('mercyv-fraks:depositTreasury', function(amount, accountType)
local source = source
local xPlayer = ESX.GetPlayerFromId(source)
local factionId, grade = GetPlayerFaction(source)
if not xPlayer or not factionId then return end
if not HasPermission(factionId, grade, 'depositTreasury') then return end
amount = math.max(1, math.floor(tonumber(amount) or 0))
accountType = (accountType == 'black_money') and 'black_money' or 'money'
local playerMoney = xPlayer.getAccount(accountType).money
if playerMoney < amount then
TriggerClientEvent('mercyv-fraks:notify', source, 'Nicht genug Geld', 'error')
return
end
xPlayer.removeAccountMoney(accountType, amount)
local bal = treasury[factionId] or { money = 0, black_money = 0 }
bal[accountType] = bal[accountType] + amount
treasury[factionId] = bal
local dbField = accountType == 'black_money' and 'balance_black_money' or 'balance_money'
MySQL.query('UPDATE mercyv_faction_treasury SET ' .. dbField .. ' = ? WHERE faction_id = ?', { bal[accountType], factionId })
MySQL.query('INSERT INTO mercyv_faction_transactions (faction_id, player_identifier, player_name, type, account_type, amount) VALUES (?, ?, ?, ?, ?, ?)', {
factionId, GetIdentifier(source), GetPlayerName(source), 'deposit', accountType, amount
})
TriggerClientEvent('mercyv-fraks:notify', source, '$' .. amount .. ' eingezahlt', 'success')
TriggerClientEvent('mercyv-fraks:updateTreasury', source, bal)
SendWebhook("Kasse Einzahlung", string.format(
"Fraktion: **%s**\nSpieler: **%s**\nBetrag: **$%d** (%s)\nNeuer Stand: **$%d** / **$%d**",
Config.Factions[factionId].label, GetPlayerName(source), amount,
accountType == 'black_money' and 'Schwarzgeld' or 'Bargeld',
bal.money, bal.black_money
), 3066993)
end)
RegisterNetEvent('mercyv-fraks:withdrawTreasury')
AddEventHandler('mercyv-fraks:withdrawTreasury', function(amount, accountType)
local source = source
local xPlayer = ESX.GetPlayerFromId(source)
local factionId, grade = GetPlayerFaction(source)
if not xPlayer or not factionId then return end
if not HasPermission(factionId, grade, 'withdrawTreasury') then return end
amount = math.max(1, math.floor(tonumber(amount) or 0))
accountType = (accountType == 'black_money') and 'black_money' or 'money'
local bal = treasury[factionId] or { money = 0, black_money = 0 }
if bal[accountType] < amount then
TriggerClientEvent('mercyv-fraks:notify', source, 'Nicht genug in der Kasse', 'error')
return
end
bal[accountType] = bal[accountType] - amount
treasury[factionId] = bal
xPlayer.addAccountMoney(accountType, amount)
local dbField = accountType == 'black_money' and 'balance_black_money' or 'balance_money'
MySQL.query('UPDATE mercyv_faction_treasury SET ' .. dbField .. ' = ? WHERE faction_id = ?', { bal[accountType], factionId })
MySQL.query('INSERT INTO mercyv_faction_transactions (faction_id, player_identifier, player_name, type, account_type, amount) VALUES (?, ?, ?, ?, ?, ?)', {
factionId, GetIdentifier(source), GetPlayerName(source), 'withdraw', accountType, amount
})
TriggerClientEvent('mercyv-fraks:notify', source, '$' .. amount .. ' ausgezahlt', 'success')
TriggerClientEvent('mercyv-fraks:updateTreasury', source, bal)
SendWebhook("Kasse Auszahlung", string.format(
"Fraktion: **%s**\nSpieler: **%s**\nBetrag: **$%d** (%s)\nNeuer Stand: **$%d** / **$%d**",
Config.Factions[factionId].label, GetPlayerName(source), amount,
accountType == 'black_money' and 'Schwarzgeld' or 'Bargeld',
bal.money, bal.black_money
), 15158332)
end)
-- =====================
-- STASH (codem-inventory)
-- =====================
ESX.RegisterServerCallback('mercyv-fraks:canUseStash', function(source, cb)
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'useStash') then return cb(false) end
cb(true)
end)
-- =====================
-- GARAGE SYSTEM (mit Jaksam Vehicle Keys)
-- =====================
-- Callback: Garage-Daten fuer NUI
ESX.RegisterServerCallback('mercyv-fraks:getGarageData', function(source, cb)
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'useGarage') then return cb(nil) end
local config = Config.Factions[factionId]
if not config then return cb(nil) end
-- Nur Fahrzeuge die der Spieler mit seinem Rang nutzen darf
local availableVehicles = {}
for _, v in ipairs(config.vehicles or {}) do
if grade >= (v.minGrade or 0) then
availableVehicles[#availableVehicles + 1] = {
model = v.model,
label = v.label,
minGrade = v.minGrade or 0,
image = v.image or '',
}
end
end
cb({
vehicles = availableVehicles,
theme = config.theme or {},
factionLabel = config.label,
hasSpawned = spawnedVehicles[source] ~= nil,
})
end)
-- Ausparken (Fahrzeug spawnen + Schluessel geben)
RegisterNetEvent('mercyv-fraks:garageSpawn')
AddEventHandler('mercyv-fraks:garageSpawn', function(vehicleModel)
local source = source
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'useGarage') then return end
local config = Config.Factions[factionId]
if not config or not config.vehicles then return end
-- Bereits ein Fahrzeug ausgeparkt?
if spawnedVehicles[source] then
TriggerClientEvent('mercyv-fraks:notify', source, 'Du hast bereits ein Fahrzeug ausgeparkt', 'error')
return
end
-- Fahrzeug in Config finden
local vehConfig = nil
for _, v in ipairs(config.vehicles) do
if v.model == vehicleModel then
vehConfig = v
break
end
end
if not vehConfig then
TriggerClientEvent('mercyv-fraks:notify', source, 'Fahrzeug nicht gefunden', 'error')
return
end
if grade < (vehConfig.minGrade or 0) then
TriggerClientEvent('mercyv-fraks:notify', source, 'Rang nicht ausreichend', 'error')
return
end
-- Farbe bestimmen: Fahrzeug-spezifisch oder Fraktions-Default
local color = vehConfig.color or config.vehicleColor or nil
-- Client soll Fahrzeug spawnen
TriggerClientEvent('mercyv-fraks:doSpawnVehicle', source, vehicleModel, config.garageSpawn, color, vehConfig.livery)
TriggerClientEvent('mercyv-fraks:notify', source, vehConfig.label .. ' wird ausgeparkt...', 'success')
end)
-- Fahrzeug wurde vom Client gespawnt -> Schluessel geben
RegisterNetEvent('mercyv-fraks:vehicleSpawned')
AddEventHandler('mercyv-fraks:vehicleSpawned', function(netId, plate)
local source = source
local factionId = GetPlayerFaction(source)
if not factionId then return end
spawnedVehicles[source] = { netId = netId, faction_id = factionId, plate = plate }
-- Jaksam Vehicle Keys: Allen Online-Fraktionsmitgliedern Schluessel geben
for _, xPlayer in pairs(ESX.GetExtendedPlayers()) do
local job = xPlayer.getJob()
if job and job.name == factionId then
pcall(function()
exports['vehicles_keys']:giveVehicleKeysToPlayerId(xPlayer.source, plate, 'temporary')
end)
end
end
TriggerClientEvent('mercyv-fraks:notify', source, 'Schluessel an alle Mitglieder verteilt', 'success')
end)
-- Einparken (Fahrzeug despawnen + Schluessel entfernen)
-- Jedes Fraktionsmitglied darf einparken, nicht nur der Spawner
RegisterNetEvent('mercyv-fraks:garageStore')
AddEventHandler('mercyv-fraks:garageStore', function()
local source = source
local factionId = GetPlayerFaction(source)
if not factionId then return end
-- Eigenes Fahrzeug oder Fahrzeug eines Fraktionsmitglieds finden
local ownerSource = nil
local vehData = nil
if spawnedVehicles[source] then
ownerSource = source
vehData = spawnedVehicles[source]
else
for src, data in pairs(spawnedVehicles) do
if data.faction_id == factionId then
ownerSource = src
vehData = data
break
end
end
end
if not vehData then
TriggerClientEvent('mercyv-fraks:notify', source, 'Kein Fraktionsfahrzeug ausgeparkt', 'error')
return
end
-- Jaksam Vehicle Keys: Schluessel bei allen Fraktionsmitgliedern entfernen
for _, xPlayer in pairs(ESX.GetExtendedPlayers()) do
local job = xPlayer.getJob()
if job and job.name == vehData.faction_id then
pcall(function()
exports['vehicles_keys']:removeKeysFromPlayerId(xPlayer.source, vehData.plate)
end)
end
end
-- Fahrzeug loeschen (broadcast an alle Clients)
TriggerClientEvent('mercyv-fraks:despawnVehicle', -1, vehData.netId)
spawnedVehicles[ownerSource] = nil
TriggerClientEvent('mercyv-fraks:notify', source, 'Fahrzeug eingeparkt, Schluessel abgegeben', 'success')
end)
-- Cleanup on disconnect
AddEventHandler('playerDropped', function()
local source = source
if spawnedVehicles[source] then
local vehData = spawnedVehicles[source]
-- Schluessel bei allen Fraktionsmitgliedern entfernen
for _, xPlayer in pairs(ESX.GetExtendedPlayers()) do
local job = xPlayer.getJob()
if job and job.name == vehData.faction_id then
pcall(function()
exports['vehicles_keys']:removeKeysFromPlayerId(xPlayer.source, vehData.plate)
end)
end
end
-- Fahrzeug loeschen
TriggerClientEvent('mercyv-fraks:despawnVehicle', -1, vehData.netId)
spawnedVehicles[source] = nil
end
end)
-- =====================
-- PERMISSIONS MANAGEMENT
-- =====================
RegisterNetEvent('mercyv-fraks:updatePermission')
AddEventHandler('mercyv-fraks:updatePermission', function(permKey, newMinGrade)
local source = source
local factionId, grade = GetPlayerFaction(source)
if not factionId or not HasPermission(factionId, grade, 'managePermissions') then return end
newMinGrade = math.max(0, math.floor(tonumber(newMinGrade) or 0))
if not Config.DefaultPermissions[permKey] then return end
if not permissions[factionId] then permissions[factionId] = {} end
permissions[factionId][permKey] = newMinGrade
MySQL.query('INSERT INTO mercyv_faction_permissions (faction_id, permission_key, min_grade) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE min_grade = ?', {
factionId, permKey, newMinGrade, newMinGrade
})
TriggerClientEvent('mercyv-fraks:notify', source, 'Recht "' .. (Config.PermissionLabels[permKey] or permKey) .. '" aktualisiert', 'success')
SendWebhook("Rechte Geaendert", string.format(
"Fraktion: **%s**\nRecht: **%s**\nNeuer Min-Rang: **%d**\nVon: **%s**",
Config.Factions[factionId].label, Config.PermissionLabels[permKey] or permKey, newMinGrade, GetPlayerName(source)
), 10181046)
end)
-- =====================
-- SYNC
-- =====================
RegisterNetEvent('mercyv-fraks:requestSync')
AddEventHandler('mercyv-fraks:requestSync', function()
-- Nothing to sync globally, data is per-faction
end)