778 lines
29 KiB
Lua
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)
|