423 lines
13 KiB
Lua
423 lines
13 KiB
Lua
ESX = exports['es_extended']:getSharedObject()
|
|
|
|
local isNuiOpen = false
|
|
local currentFactionId = nil
|
|
local pendingInvite = nil
|
|
local isGarageOpen = false
|
|
local spawnedNpcs = {} -- key -> ped handle
|
|
local playerLoaded = false
|
|
|
|
-- PlayerData laden (event-basiert, kein Polling)
|
|
Citizen.CreateThread(function()
|
|
-- Einmaliger Check bei ensure/restart (Spieler ist evtl. schon geladen)
|
|
local pd = ESX.GetPlayerData()
|
|
if pd and pd.job and pd.job.name then
|
|
ESX.PlayerData = pd
|
|
playerLoaded = true
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('esx:playerLoaded')
|
|
AddEventHandler('esx:playerLoaded', function(xPlayer)
|
|
ESX.PlayerData = xPlayer
|
|
playerLoaded = true
|
|
end)
|
|
|
|
-- =====================
|
|
-- NPC SPAWNING
|
|
-- =====================
|
|
function SpawnNPC(key, coords, model, scenario)
|
|
if spawnedNpcs[key] then return end
|
|
|
|
local hash = GetHashKey(model)
|
|
RequestModel(hash)
|
|
while not HasModelLoaded(hash) do Citizen.Wait(10) end
|
|
|
|
local ped = CreatePed(4, hash, coords.x, coords.y, coords.z - 1.0, coords.w, false, true)
|
|
SetEntityHeading(ped, coords.w)
|
|
FreezeEntityPosition(ped, true)
|
|
SetEntityInvincible(ped, true)
|
|
SetBlockingOfNonTemporaryEvents(ped, true)
|
|
TaskStartScenarioInPlace(ped, scenario, 0, true)
|
|
SetModelAsNoLongerNeeded(hash)
|
|
|
|
spawnedNpcs[key] = ped
|
|
end
|
|
|
|
-- Spawn NPCs fuer alle Fraktionen bei Resource-Start
|
|
Citizen.CreateThread(function()
|
|
Citizen.Wait(2000)
|
|
for factionId, config in pairs(Config.Factions) do
|
|
-- Garage NPC
|
|
if config.garageNpc and config.garage then
|
|
local model = config.garageNpcModel or Config.DefaultGarageNpcModel
|
|
local scenario = config.garageNpcScenario or Config.DefaultGarageNpcScenario
|
|
SpawnNPC('garage_' .. factionId, config.garage, model, scenario)
|
|
end
|
|
-- Stash NPC
|
|
if config.stashNpc and config.stash then
|
|
local model = config.stashNpcModel or Config.DefaultStashNpcModel
|
|
local scenario = config.stashNpcScenario or Config.DefaultStashNpcScenario
|
|
SpawnNPC('stash_' .. factionId, config.stash, model, scenario)
|
|
end
|
|
end
|
|
end)
|
|
|
|
-- Cleanup on resource stop
|
|
AddEventHandler('onResourceStop', function(resourceName)
|
|
if resourceName ~= GetCurrentResourceName() then return end
|
|
for _, ped in pairs(spawnedNpcs) do
|
|
if DoesEntityExist(ped) then DeleteEntity(ped) end
|
|
end
|
|
end)
|
|
|
|
-- =====================
|
|
-- INTERACTION THREAD (Computer + Garage + Stash)
|
|
-- =====================
|
|
Citizen.CreateThread(function()
|
|
-- Warte bis ESX PlayerData geladen ist
|
|
while not playerLoaded do
|
|
Citizen.Wait(500)
|
|
end
|
|
|
|
local nearPoint = false
|
|
local COARSE_DIST = 50.0
|
|
|
|
while true do
|
|
local sleep = 1000
|
|
local playerCoords = GetEntityCoords(PlayerPedId())
|
|
local foundNear = false
|
|
local playerJob = ESX.PlayerData.job
|
|
|
|
if not isNuiOpen and not isGarageOpen and playerJob and playerJob.name then
|
|
local factionConfig = Config.Factions[playerJob.name]
|
|
|
|
if factionConfig then
|
|
-- Computer Check
|
|
if factionConfig.computer then
|
|
local compCoords = factionConfig.computer
|
|
local dist = #(playerCoords - vector3(compCoords.x, compCoords.y, compCoords.z))
|
|
|
|
if dist <= Config.InteractionDistance then
|
|
if playerJob.grade >= (factionConfig.minComputerGrade or 0) then
|
|
sleep = 0
|
|
foundNear = true
|
|
exports[Config.HelpNotify.export][Config.HelpNotify.showFunc]('Fraktions-Computer', 'E')
|
|
if IsControlJustReleased(0, 38) then
|
|
OpenFactionMenu(playerJob.name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Garage Check (separater Standort)
|
|
if not foundNear and factionConfig.garage then
|
|
local garageCoords = factionConfig.garage
|
|
local dist = #(playerCoords - vector3(garageCoords.x, garageCoords.y, garageCoords.z))
|
|
|
|
if dist <= Config.InteractionDistance then
|
|
sleep = 0
|
|
foundNear = true
|
|
exports[Config.HelpNotify.export][Config.HelpNotify.showFunc]('Fraktions-Garage', 'E')
|
|
if IsControlJustReleased(0, 38) then
|
|
OpenGarageMenu(playerJob.name)
|
|
end
|
|
end
|
|
end
|
|
|
|
-- Stash Check (separater Standort)
|
|
if not foundNear and factionConfig.stash then
|
|
local stashCoords = factionConfig.stash
|
|
local dist = #(playerCoords - vector3(stashCoords.x, stashCoords.y, stashCoords.z))
|
|
|
|
if dist <= Config.InteractionDistance then
|
|
sleep = 0
|
|
foundNear = true
|
|
exports[Config.HelpNotify.export][Config.HelpNotify.showFunc]('Fraktions-Lager', 'E')
|
|
if IsControlJustReleased(0, 38) then
|
|
OpenFactionStash(playerJob.name)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if not foundNear and nearPoint then
|
|
exports[Config.HelpNotify.export][Config.HelpNotify.hideFunc]()
|
|
end
|
|
nearPoint = foundNear
|
|
|
|
Citizen.Wait(sleep)
|
|
end
|
|
end)
|
|
|
|
-- =====================
|
|
-- NUI MANAGEMENT
|
|
-- =====================
|
|
function OpenFactionMenu(factionId)
|
|
if isNuiOpen then return end
|
|
|
|
ESX.TriggerServerCallback('mercyv-fraks:getFactionData', function(data)
|
|
if not data then return end
|
|
|
|
isNuiOpen = true
|
|
currentFactionId = factionId
|
|
SetNuiFocus(true, true)
|
|
SendNUIMessage({
|
|
type = 'open',
|
|
factionId = data.factionId,
|
|
factionLabel = data.factionLabel,
|
|
factionImage = data.factionImage or '',
|
|
playerGrade = data.playerGrade,
|
|
grades = data.grades,
|
|
treasury = data.treasury,
|
|
totalMembers = data.totalMembers,
|
|
onlineMembers = data.onlineMembers,
|
|
permissions = data.permissions,
|
|
permissionValues = data.permissionValues,
|
|
permissionLabels = Config.PermissionLabels,
|
|
theme = data.theme,
|
|
vehicles = data.vehicles,
|
|
})
|
|
end)
|
|
end
|
|
|
|
function CloseMenu()
|
|
if not isNuiOpen then return end
|
|
isNuiOpen = false
|
|
currentFactionId = nil
|
|
SetNuiFocus(false, false)
|
|
SendNUIMessage({ type = 'close' })
|
|
end
|
|
|
|
-- =====================
|
|
-- NUI CALLBACKS
|
|
-- =====================
|
|
RegisterNUICallback('close', function(_, cb)
|
|
CloseMenu()
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('getMembers', function(_, cb)
|
|
ESX.TriggerServerCallback('mercyv-fraks:getMembers', function(members)
|
|
SendNUIMessage({ type = 'updateMembers', members = members })
|
|
end)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('getTreasuryLog', function(_, cb)
|
|
ESX.TriggerServerCallback('mercyv-fraks:getTreasuryLog', function(log)
|
|
SendNUIMessage({ type = 'updateTreasuryLog', log = log })
|
|
end)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('getNearbyPlayers', function(_, cb)
|
|
ESX.TriggerServerCallback('mercyv-fraks:getNearbyPlayers', function(players)
|
|
SendNUIMessage({ type = 'updateNearbyPlayers', players = players })
|
|
end)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('inviteMember', function(data, cb)
|
|
TriggerServerEvent('mercyv-fraks:inviteMember', data.targetSource)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('kickMember', function(data, cb)
|
|
TriggerServerEvent('mercyv-fraks:kickMember', data.identifier)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('changeRank', function(data, cb)
|
|
TriggerServerEvent('mercyv-fraks:changeRank', data.identifier, data.grade)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('depositTreasury', function(data, cb)
|
|
TriggerServerEvent('mercyv-fraks:depositTreasury', data.amount, data.accountType)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('withdrawTreasury', function(data, cb)
|
|
TriggerServerEvent('mercyv-fraks:withdrawTreasury', data.amount, data.accountType)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('garageSpawn', function(data, cb)
|
|
TriggerServerEvent('mercyv-fraks:garageSpawn', data.model)
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('garageStore', function(_, cb)
|
|
TriggerServerEvent('mercyv-fraks:garageStore')
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('closeGarage', function(_, cb)
|
|
CloseGarage()
|
|
cb('ok')
|
|
end)
|
|
|
|
RegisterNUICallback('updatePermission', function(data, cb)
|
|
TriggerServerEvent('mercyv-fraks:updatePermission', data.permKey, data.minGrade)
|
|
cb('ok')
|
|
end)
|
|
|
|
-- =====================
|
|
-- SERVER EVENT HANDLERS
|
|
-- =====================
|
|
RegisterNetEvent('mercyv-fraks:notify')
|
|
AddEventHandler('mercyv-fraks:notify', function(message, nType)
|
|
TriggerEvent(Config.Notification.event, Config.Notification.title, message, nType or 'info', Config.Notification.duration)
|
|
end)
|
|
|
|
RegisterNetEvent('mercyv-fraks:updateTreasury')
|
|
AddEventHandler('mercyv-fraks:updateTreasury', function(bal)
|
|
if isNuiOpen then
|
|
SendNUIMessage({ type = 'updateTreasury', treasury = bal })
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent('mercyv-fraks:forceClose')
|
|
AddEventHandler('mercyv-fraks:forceClose', function()
|
|
CloseMenu()
|
|
end)
|
|
|
|
-- =====================
|
|
-- INVITE SYSTEM (Client-Side)
|
|
-- =====================
|
|
RegisterNetEvent('mercyv-fraks:receiveInvite')
|
|
AddEventHandler('mercyv-fraks:receiveInvite', function(inviteData)
|
|
pendingInvite = inviteData
|
|
SetNuiFocus(true, true)
|
|
SendNUIMessage({
|
|
type = 'showInvite',
|
|
factionLabel = inviteData.factionLabel,
|
|
inviterName = inviteData.inviterName,
|
|
})
|
|
end)
|
|
|
|
RegisterNUICallback('respondInvite', function(data, cb)
|
|
SetNuiFocus(false, false)
|
|
if pendingInvite then
|
|
TriggerServerEvent('mercyv-fraks:respondInvite', data.accepted, pendingInvite.factionId, pendingInvite.inviterSource)
|
|
pendingInvite = nil
|
|
end
|
|
cb('ok')
|
|
end)
|
|
|
|
-- =====================
|
|
-- GARAGE FUNCTIONS
|
|
-- =====================
|
|
function OpenFactionStash(factionId)
|
|
if isNuiOpen or isGarageOpen then return end
|
|
|
|
-- Rechte-Check ueber Server
|
|
ESX.TriggerServerCallback('mercyv-fraks:canUseStash', function(allowed)
|
|
if not allowed then
|
|
TriggerEvent(Config.Notification.event, Config.Notification.title, 'Kein Zugriff auf das Lager', 'error', Config.Notification.duration)
|
|
return
|
|
end
|
|
|
|
local config = Config.Factions[factionId]
|
|
local stashId = 'faction_stash_' .. factionId
|
|
local slots = config.stashSlots or 50
|
|
local weight = config.stashWeight or 100000
|
|
|
|
TriggerServerEvent('codem-inventory:server:openstash', stashId, slots, weight, config.label .. ' Lager')
|
|
end)
|
|
end
|
|
|
|
function OpenGarageMenu(factionId)
|
|
if isGarageOpen or isNuiOpen then return end
|
|
|
|
ESX.TriggerServerCallback('mercyv-fraks:getGarageData', function(data)
|
|
if not data then return end
|
|
|
|
isGarageOpen = true
|
|
SetNuiFocus(true, true)
|
|
SendNUIMessage({
|
|
type = 'openGarage',
|
|
vehicles = data.vehicles,
|
|
theme = data.theme,
|
|
factionLabel = data.factionLabel,
|
|
hasSpawned = data.hasSpawned,
|
|
})
|
|
end)
|
|
end
|
|
|
|
function CloseGarage()
|
|
if not isGarageOpen then return end
|
|
isGarageOpen = false
|
|
SetNuiFocus(false, false)
|
|
SendNUIMessage({ type = 'closeGarage' })
|
|
end
|
|
|
|
-- =====================
|
|
-- VEHICLE SPAWNING (Client-Side)
|
|
-- =====================
|
|
RegisterNetEvent('mercyv-fraks:doSpawnVehicle')
|
|
AddEventHandler('mercyv-fraks:doSpawnVehicle', function(model, coords, color, livery)
|
|
local hash = GetHashKey(model)
|
|
RequestModel(hash)
|
|
|
|
local timeout = 0
|
|
while not HasModelLoaded(hash) and timeout < 50 do
|
|
Citizen.Wait(100)
|
|
timeout = timeout + 1
|
|
end
|
|
|
|
if not HasModelLoaded(hash) then
|
|
TriggerEvent(Config.Notification.event, Config.Notification.title, 'Fahrzeugmodell nicht gefunden', 'error', Config.Notification.duration)
|
|
return
|
|
end
|
|
|
|
-- ✅ SPAWN FIX
|
|
local vehicle = CreateVehicle(hash, coords.x, coords.y, coords.z, coords.w, true, false)
|
|
SetModelAsNoLongerNeeded(hash)
|
|
|
|
SetVehicleOnGroundProperly(vehicle)
|
|
SetVehicleModKit(vehicle, 0)
|
|
|
|
Citizen.Wait(200)
|
|
|
|
-- 🎨 Farbe
|
|
if color and color.primary then
|
|
SetVehicleColours(vehicle, color.primary, color.secondary or color.primary)
|
|
end
|
|
|
|
-- 🔥 LIVERY
|
|
if livery ~= nil and livery >= 0 then
|
|
SetVehicleLivery(vehicle, livery) -- Standard-Livery
|
|
SetVehicleMod(vehicle, 48, livery - 1) -- Mod-basierte Livery (die meisten Custom-Fahrzeuge)
|
|
end
|
|
|
|
-- Netzwerk + Keys
|
|
local netId = NetworkGetNetworkIdFromEntity(vehicle)
|
|
local plate = GetVehicleNumberPlateText(vehicle)
|
|
|
|
TriggerServerEvent('mercyv-fraks:vehicleSpawned', netId, plate)
|
|
|
|
-- Spieler reinsetzen
|
|
TaskWarpPedIntoVehicle(PlayerPedId(), vehicle, -1)
|
|
|
|
-- Garage schließen
|
|
CloseGarage()
|
|
end)
|
|
|
|
RegisterNetEvent('mercyv-fraks:despawnVehicle')
|
|
AddEventHandler('mercyv-fraks:despawnVehicle', function(netId)
|
|
local vehicle = NetworkGetEntityFromNetworkId(netId)
|
|
if vehicle and DoesEntityExist(vehicle) then
|
|
DeleteEntity(vehicle)
|
|
end
|
|
end)
|
|
|
|
-- ESX PlayerData sync
|
|
RegisterNetEvent('esx:setJob')
|
|
AddEventHandler('esx:setJob', function(job)
|
|
ESX.PlayerData.job = job
|
|
playerLoaded = true
|
|
end)
|