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

299 lines
9.2 KiB
Lua

ESX = exports['es_extended']:getSharedObject()
local spawnedPeds = {}
local isNuiOpen = false
local currentStation = nil
-- =====================
-- DRAW TEXT 3D
-- =====================
function DrawText3D(x, y, z, text)
SetTextScale(0.35, 0.35)
SetTextFont(4)
SetTextProportional(1)
SetTextColour(255, 255, 255, 215)
SetTextEntry('STRING')
SetTextCentre(true)
AddTextComponentString(text)
SetDrawOrigin(x, y, z, 0)
DrawText(0.0, 0.0)
local factor = (string.len(text)) / 370
DrawRect(0.0, 0.0 + 0.0125, 0.017 + factor, 0.03, 0, 0, 0, 100)
ClearDrawOrigin()
end
-- =====================
-- NPC SPAWNING
-- =====================
Citizen.CreateThread(function()
for stationId, station in pairs(Config.CraftingStations) do
local npc = station.npc
local hash = GetHashKey(npc.model)
RequestModel(hash)
while not HasModelLoaded(hash) do
Citizen.Wait(10)
end
local ped = CreatePed(4, hash, npc.coords.x, npc.coords.y, npc.coords.z - 1.0, npc.coords.w, false, true)
SetEntityHeading(ped, npc.coords.w)
FreezeEntityPosition(ped, true)
SetEntityInvincible(ped, true)
SetBlockingOfNonTemporaryEvents(ped, true)
if npc.scenario then
TaskStartScenarioInPlace(ped, npc.scenario, 0, true)
end
spawnedPeds[stationId] = ped
SetModelAsNoLongerNeeded(hash)
end
end)
-- Cleanup on resource stop
AddEventHandler('onResourceStop', function(resourceName)
if resourceName ~= GetCurrentResourceName() then return end
for _, ped in pairs(spawnedPeds) do
if DoesEntityExist(ped) then
DeleteEntity(ped)
end
end
end)
-- =====================
-- NPC INTERACTION
-- =====================
Citizen.CreateThread(function()
while true do
local sleep = 1000
local playerCoords = GetEntityCoords(PlayerPedId())
local closestStation = nil
local closestDist = Config.InteractionDistance + 1
for stationId, station in pairs(Config.CraftingStations) do
local dist = #(playerCoords - vector3(station.npc.coords.x, station.npc.coords.y, station.npc.coords.z))
if dist < closestDist then
closestDist = dist
closestStation = stationId
end
end
if closestStation and closestDist <= Config.InteractionDistance then
sleep = 0
local station = Config.CraftingStations[closestStation]
if not isNuiOpen then
-- Draw 3D text above NPC instead of spamming notifications
local npcCoords = station.npc.coords
DrawText3D(npcCoords.x, npcCoords.y, npcCoords.z + 1.0, '[E] ' .. station.label)
if IsControlJustReleased(0, 38) then -- E key
OpenCraftingMenu(closestStation)
end
end
end
Citizen.Wait(sleep)
end
end)
-- =====================
-- VEHICLE DETECTION
-- =====================
function GetOwnedVehiclesNearby(cb)
local playerCoords = GetEntityCoords(PlayerPedId())
local vehicles = GetGamePool('CVehicle')
local nearbyOwned = {}
local checksRemaining = 0
for _, vehicle in ipairs(vehicles) do
local vehCoords = GetEntityCoords(vehicle)
local dist = #(playerCoords - vehCoords)
if dist <= Config.VehicleSearchRadius then
local plate = string.gsub(GetVehicleNumberPlateText(vehicle), '%s+', '')
checksRemaining = checksRemaining + 1
ESX.TriggerServerCallback('mercyv-crafting:checkVehicleOwnership', function(isOwned)
if isOwned then
nearbyOwned[#nearbyOwned + 1] = {
vehicle = vehicle,
plate = plate,
model = GetDisplayNameFromVehicleModel(GetEntityModel(vehicle)),
}
end
checksRemaining = checksRemaining - 1
if checksRemaining <= 0 then
cb(nearbyOwned)
end
end, plate)
end
end
if checksRemaining == 0 then
cb(nearbyOwned)
end
end
-- =====================
-- NUI MANAGEMENT
-- =====================
function OpenCraftingMenu(stationId)
if isNuiOpen then return end
currentStation = stationId
local station = Config.CraftingStations[stationId]
-- Get station recipes
local recipes = {}
for _, recipeId in ipairs(station.recipes) do
local recipe = Config.Recipes[recipeId]
if recipe then
recipes[#recipes + 1] = {
id = recipeId,
label = recipe.label,
category = recipe.category,
result = recipe.result,
ingredients = recipe.ingredients,
duration = recipe.duration,
}
end
end
-- Get player inventory
ESX.TriggerServerCallback('mercyv-crafting:getPlayerItems', function(playerItems)
-- Get nearby owned vehicles and their trunk items
GetOwnedVehiclesNearby(function(vehicles)
local vehicleData = {}
if #vehicles > 0 then
local trunkChecks = #vehicles
for _, veh in ipairs(vehicles) do
ESX.TriggerServerCallback('mercyv-crafting:getTrunkItems', function(trunkItems)
vehicleData[#vehicleData + 1] = {
plate = veh.plate,
model = veh.model,
items = trunkItems,
}
trunkChecks = trunkChecks - 1
if trunkChecks <= 0 then
SendCraftingData(station, recipes, playerItems, vehicleData)
end
end, veh.plate)
end
else
SendCraftingData(station, recipes, playerItems, vehicleData)
end
end)
end)
end
function SendCraftingData(station, recipes, playerItems, vehicleData)
isNuiOpen = true
SetNuiFocus(true, true)
SendNUIMessage({
type = 'open',
stationLabel = station.label,
stationImage = station.image or '',
stationColor = station.color or '',
recipes = recipes,
playerItems = playerItems,
vehicles = vehicleData,
})
-- Check if there are active crafts running at this station
ESX.TriggerServerCallback('mercyv-crafting:getCraftStatus', function(statuses)
if statuses then
SendNUIMessage({
type = 'craftStatuses',
statuses = statuses,
})
end
end)
end
function CloseCraftingMenu()
if not isNuiOpen then return end
isNuiOpen = false
currentStation = nil
SetNuiFocus(false, false)
SendNUIMessage({ type = 'close' })
end
-- =====================
-- NUI CALLBACKS
-- =====================
RegisterNUICallback('close', function(_, cb)
CloseCraftingMenu()
cb('ok')
end)
RegisterNUICallback('startCraft', function(data, cb)
if not currentStation then
cb({ success = false, message = 'Keine Station ausgewaehlt' })
return
end
TriggerServerEvent('mercyv-crafting:startCraft', data.recipeId, currentStation, data.useSource, data.craftAmount or 1)
cb('ok')
end)
RegisterNUICallback('collectCraft', function(data, cb)
TriggerServerEvent('mercyv-crafting:collectCraft', data.recipeId, data.collectTo, data.collectAmount or 1)
cb('ok')
end)
-- =====================
-- SERVER EVENT HANDLERS
-- =====================
RegisterNetEvent('mercyv-crafting:craftResult')
AddEventHandler('mercyv-crafting:craftResult', function(success, message)
if success then
TriggerEvent('hex_4_hud:notify', 'Crafting', message, 'success', 3000)
else
TriggerEvent('hex_4_hud:notify', 'Crafting', message, 'error', 3000)
SendNUIMessage({ type = 'craftingFailed', message = message })
end
end)
RegisterNetEvent('mercyv-crafting:craftStatusUpdate')
AddEventHandler('mercyv-crafting:craftStatusUpdate', function(status)
SendNUIMessage({ type = 'craftStatusUpdate', status = status })
end)
RegisterNetEvent('mercyv-crafting:collectResult')
AddEventHandler('mercyv-crafting:collectResult', function(success, message, recipeId)
if success then
TriggerEvent('hex_4_hud:notify', 'Crafting', message, 'success', 3000)
SendNUIMessage({ type = 'collectSuccess', recipeId = recipeId })
else
TriggerEvent('hex_4_hud:notify', 'Crafting', message, 'error', 3000)
end
end)
CreateThread(function()
for k, station in pairs(Config.CraftingStations) do
if station.blip and station.blip.enabled then
local blip = AddBlipForCoord(station.npc.coords.x, station.npc.coords.y, station.npc.coords.z)
SetBlipSprite(blip, station.blip.sprite or 1)
SetBlipDisplay(blip, 4)
SetBlipScale(blip, station.blip.scale or 1.0)
SetBlipColour(blip, station.blip.color or 1)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING")
AddTextComponentString(station.label)
EndTextCommandSetBlipName(blip)
end
end
end)