Files
2026-08-05 01:29:39 +02:00

386 lines
16 KiB
Lua

local progress = { drill = false, card = false, vehicle = false }
local robberyRunning = false
local activePrep = nil
local objectiveProp = nil
local plannerPeds = {}
local warehousePeds = {}
local missionProps = {}
local missionVehicles = {}
local cutsceneRunning = false
local function notify(message) exports.devx_core:Notify(message) end
local function help(message)
BeginTextCommandDisplayHelp('STRING')
AddTextComponentSubstringPlayerName(message)
EndTextCommandDisplayHelp(0, false, true, -1)
end
local function subtitle(message, duration)
BeginTextCommandPrint('STRING')
AddTextComponentSubstringPlayerName(message)
EndTextCommandPrint(duration or 3500, true)
end
local function drawMarkerAt(coords, red, green, blue)
DrawMarker(1, coords.x, coords.y, coords.z - 1.0, 0.0,0.0,0.0,0.0,0.0,0.0,
1.2,1.2,0.35,red,green,blue,130,false,false,2,false,nil,nil,false)
end
local function loadModel(name)
local hash = GetHashKey(name)
RequestModel(hash)
local deadline = GetGameTimer() + 7000
while not HasModelLoaded(hash) and GetGameTimer() < deadline do Wait(0) end
return HasModelLoaded(hash) and hash or nil
end
local function loadAnim(dict)
RequestAnimDict(dict)
local deadline = GetGameTimer() + 5000
while not HasAnimDictLoaded(dict) and GetGameTimer() < deadline do Wait(0) end
return HasAnimDictLoaded(dict)
end
local function teleport(coords)
DoScreenFadeOut(250)
local deadline = GetGameTimer() + 1500
while not IsScreenFadedOut() and GetGameTimer() < deadline do Wait(0) end
local ped = PlayerPedId()
FreezeEntityPosition(ped, true)
if coords.z < -10.0 then SetFocusPosAndVel(coords.x, coords.y, coords.z, 0.0, 0.0, 0.0) end
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
SetEntityCoordsNoOffset(ped, coords.x, coords.y, coords.z, false, false, false)
SetEntityHeading(ped, coords.w or coords.heading or 0.0)
local collisionDeadline = GetGameTimer() + 5000
while not HasCollisionLoadedAroundEntity(ped) and GetGameTimer() < collisionDeadline do
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
Wait(0)
end
ClearPedTasksImmediately(ped)
FreezeEntityPosition(ped, false)
Wait(180)
ClearFocus()
DoScreenFadeIn(350)
end
local function ensurePrepInterior()
local interior = DevXCrimeConfig.PrepInterior
local position = interior.loadAt or interior.spawn
SetFocusPosAndVel(position.x, position.y, position.z, 0.0, 0.0, 0.0)
RequestCollisionAtCoord(position.x, position.y, position.z)
RequestAdditionalCollisionAtCoord(position.x, position.y, position.z)
NewLoadSceneStartSphere(position.x, position.y, position.z, 110.0, 0)
local interiorId = GetInteriorAtCoords(position.x, position.y, position.z)
if interiorId ~= 0 then
PinInteriorInMemory(interiorId)
RefreshInterior(interiorId)
end
local deadline = GetGameTimer() + 12000
local ready = false
while GetGameTimer() < deadline do
local sceneReady = IsNewLoadSceneLoaded()
local interiorReady = interiorId == 0 or IsInteriorReady(interiorId)
if sceneReady and interiorReady then
ready = true
break
end
RequestCollisionAtCoord(position.x, position.y, position.z)
Wait(0)
end
NewLoadSceneStop()
ClearFocus()
if not ready then
print(('[devx_crime] Sicheres Garagen-Interior nicht bereit. interior=%s'):format(interiorId))
end
return ready
end
local function deletePeds(list)
for _, ped in pairs(list) do
if ped and DoesEntityExist(ped) then DeleteEntity(ped) end
end
for key in pairs(list) do list[key] = nil end
end
local function deleteProps(list)
for _, object in pairs(list) do
if object and DoesEntityExist(object) then DeleteEntity(object) end
end
for key in pairs(list) do list[key] = nil end
end
local function deleteVehicles(list)
for _, vehicle in pairs(list) do
if vehicle and DoesEntityExist(vehicle) then DeleteEntity(vehicle) end
end
for key in pairs(list) do list[key] = nil end
end
local function spawnMissionVehicles()
deleteVehicles(missionVehicles)
for index, entry in ipairs(DevXCrimeConfig.PrepInterior.vehicles or {}) do
local model = loadModel(entry.model)
if model then
local c = entry.coords
local vehicle = CreateVehicle(model, c.x, c.y, c.z, c.w or 0.0, false, false)
SetVehicleOnGroundProperly(vehicle)
SetVehicleDoorsLocked(vehicle, 2)
SetEntityInvincible(vehicle, true)
FreezeEntityPosition(vehicle, true)
missionVehicles[index] = vehicle
SetModelAsNoLongerNeeded(model)
end
end
end
local function spawnMissionSetDressing()
deleteProps(missionProps)
for index, entry in ipairs(DevXCrimeConfig.PrepInterior.setDressing or {}) do
local model = loadModel(entry.model)
if model then
local c = entry.coords
local object = CreateObject(model, c.x, c.y, c.z, false, false, false)
SetEntityHeading(object, c.w or 0.0)
FreezeEntityPosition(object, true)
SetEntityCollision(object, true, true)
missionProps[index] = object
SetModelAsNoLongerNeeded(model)
end
end
end
local function spawnPlanners()
deletePeds(plannerPeds)
local model = loadModel(DevXCrimeConfig.PlannerModel)
if not model then return end
for _, prep in ipairs(DevXCrimeConfig.PrepLocations) do
local c = prep.planner
local ped = CreatePed(4, model, c.x, c.y, c.z - 1.0, c.w, false, false)
SetEntityInvincible(ped, true)
SetBlockingOfNonTemporaryEvents(ped, true)
FreezeEntityPosition(ped, true)
TaskStartScenarioInPlace(ped, 'WORLD_HUMAN_CLIPBOARD', 0, true)
plannerPeds[prep.key] = ped
end
SetModelAsNoLongerNeeded(model)
end
local function spawnWarehousePeds()
deletePeds(warehousePeds)
for index, worker in ipairs(DevXCrimeConfig.WarehouseWorkers or {}) do
local model = loadModel(worker.model)
if model then
local c = worker.coords
local ped = CreatePed(4, model, c.x, c.y, c.z, c.w, false, false)
SetEntityInvincible(ped, true)
SetBlockingOfNonTemporaryEvents(ped, true)
FreezeEntityPosition(ped, true)
TaskStartScenarioInPlace(ped, worker.scenario or 'WORLD_HUMAN_CLIPBOARD', 0, true)
warehousePeds[index] = ped
SetModelAsNoLongerNeeded(model)
end
end
end
local function removeObjectiveProp()
if objectiveProp and DoesEntityExist(objectiveProp) then DeleteEntity(objectiveProp) end
objectiveProp = nil
end
local function spawnObjectiveProp(prep)
removeObjectiveProp()
local model = loadModel(prep.prop)
if not model then return end
objectiveProp = CreateObject(model, prep.objective.x, prep.objective.y, prep.objective.z + 0.05, false, false, false)
SetEntityHeading(objectiveProp, prep.objective.w or 0.0)
FreezeEntityPosition(objectiveProp, true)
SetModelAsNoLongerNeeded(model)
end
local function timedAction(label, seconds)
local endTime = GetGameTimer() + seconds * 1000
FreezeEntityPosition(PlayerPedId(), true)
while GetGameTimer() < endTime do
local left = math.ceil((endTime - GetGameTimer()) / 1000)
help(('%s ~b~%ss'):format(label, left))
DisableAllControlActions(0)
EnableControlAction(0, 200, true)
Wait(0)
end
FreezeEntityPosition(PlayerPedId(), false)
end
local function refreshStatus()
DevXTriggerCallback('crime:status', {}, function(result)
if result.ok then progress = result.progress or progress end
end)
end
local function leavePrep()
if not activePrep then return end
DevXTriggerCallback('crime:leavePrep', {}, function(result)
if result.ok then
teleport(result.exterior)
TriggerEvent('devx_ipl:client:restoreWorld', 'crime-leave')
end
end)
activePrep = nil
removeObjectiveProp()
deletePeds(warehousePeds)
deleteProps(missionProps)
deleteVehicles(missionVehicles)
end
local function runPlanningScene(prep, callback)
if cutsceneRunning then return end
cutsceneRunning = true
local planner = plannerPeds[prep.key]
if not planner or not DoesEntityExist(planner) then
cutsceneRunning = false
callback()
return
end
local player = PlayerPedId()
FreezeEntityPosition(player, true)
ClearPedTasksImmediately(planner)
TaskTurnPedToFaceEntity(player, planner, 1500)
TaskTurnPedToFaceEntity(planner, player, 1500)
Wait(1000)
local playerCoords = GetEntityCoords(player)
local plannerCoords = GetEntityCoords(planner)
local midpoint = (playerCoords + plannerCoords) / 2.0
local cam = CreateCam('DEFAULT_SCRIPTED_CAMERA', true)
SetCamCoord(cam, midpoint.x + 2.4, midpoint.y + 2.4, midpoint.z + 1.45)
PointCamAtCoord(cam, midpoint.x, midpoint.y, midpoint.z + 0.85)
SetCamFov(cam, 43.0)
SetCamActive(cam, true)
RenderScriptCams(true, true, 600, true, true)
if loadAnim('gestures@m@standing@casual') then
for _, line in ipairs(prep.dialogue or {}) do
TaskPlayAnim(planner, 'gestures@m@standing@casual', 'gesture_hand_down', 2.0, -2.0, 2800, 49, 0.0, false, false, false)
subtitle(('~b~%s:~s~ %s'):format(DevXCrimeConfig.PlannerName, line), 3300)
Wait(3400)
end
else
for _, line in ipairs(prep.dialogue or {}) do
subtitle(('~b~%s:~s~ %s'):format(DevXCrimeConfig.PlannerName, line), 3300)
Wait(3400)
end
end
RenderScriptCams(false, true, 550, true, true)
DestroyCam(cam, false)
FreezeEntityPosition(player, false)
TaskStartScenarioInPlace(planner, 'WORLD_HUMAN_CLIPBOARD', 0, true)
cutsceneRunning = false
callback()
end
local function beginPrep(prep)
runPlanningScene(prep, function()
DevXTriggerCallback('crime:enterPrep', { key = prep.key }, function(result)
if not result.ok then notify(result.error); return end
if not ensurePrepInterior() then
notify('Das Missionslager konnte nicht geladen werden. Die Mission wurde sicher abgebrochen.')
DevXTriggerCallback('crime:leavePrep', {}, function() end)
return
end
activePrep = result.prep
teleport(result.interior.spawn)
spawnMissionSetDressing()
spawnMissionVehicles()
spawnObjectiveProp(activePrep)
spawnWarehousePeds()
subtitle(('~b~MISSION:~s~ %s'):format(activePrep.objectiveLabel), 4500)
end)
end)
end
CreateThread(function()
ensurePrepInterior()
while not exports.devx_core:IsPlayerLoaded() do Wait(500) end
refreshStatus()
spawnPlanners()
for _, entry in ipairs(DevXCrimeConfig.PrepLocations) do
local blip = AddBlipForCoord(entry.exterior.x, entry.exterior.y, entry.exterior.z)
SetBlipSprite(blip,478);SetBlipScale(blip,.65);SetBlipColour(blip,47);SetBlipAsShortRange(blip,true)
BeginTextCommandSetBlipName('STRING');AddTextComponentString('Bankraub-Vorbereitung');EndTextCommandSetBlipName(blip)
end
end)
CreateThread(function()
while true do
local sleep = 700
if exports.devx_core:IsPlayerLoaded() and not robberyRunning and not cutsceneRunning then
local coords = GetEntityCoords(PlayerPedId())
if activePrep then
local objective = activePrep.objective
local objectiveDistance = #(coords - vector3(objective.x, objective.y, objective.z))
local exit = DevXCrimeConfig.PrepInterior.exit
local exitDistance = #(coords - exit)
if objectiveDistance < 15.0 then
sleep = 0;drawMarkerAt(objective,70,150,245)
if objectiveDistance < 1.6 then
help(('~INPUT_CONTEXT~ %s'):format(activePrep.objectiveLabel))
if IsControlJustReleased(0,38) then
timedAction(activePrep.objectiveLabel,7)
DevXTriggerCallback('crime:completePrep',{key=activePrep.key},function(result)
if result.ok then
progress=result.progress;removeObjectiveProp();deletePeds(warehousePeds);deleteProps(missionProps);deleteVehicles(missionVehicles);activePrep=nil;teleport(result.exterior);TriggerEvent('devx_ipl:client:restoreWorld', 'crime-complete')
notify('Vorbereitungsmission abgeschlossen.')
else notify(result.error or 'Mission fehlgeschlagen.') end
end)
end
end
end
if exitDistance < 8.0 then
sleep=0;drawMarkerAt(exit,230,230,230)
if exitDistance < 1.6 then
help('~INPUT_CONTEXT~ Lager verlassen')
if IsControlJustReleased(0,38) then leavePrep() end
end
end
else
for _, entry in ipairs(DevXCrimeConfig.PrepLocations) do
local distance = #(coords - vector3(entry.exterior.x,entry.exterior.y,entry.exterior.z))
if distance < 18.0 then
sleep=0;drawMarkerAt(entry.exterior,progress[entry.key] and 70 or 230,progress[entry.key] and 190 or 150,90)
if distance < 1.8 then
if progress[entry.key] then help(('Vorbereitung abgeschlossen: %s'):format(entry.label))
else
help(('~INPUT_CONTEXT~ mit %s planen: %s'):format(DevXCrimeConfig.PlannerName, entry.label))
if IsControlJustReleased(0,38) then beginPrep(entry) end
end
end
end
end
local bankDistance=#(coords-DevXCrimeConfig.Bank)
if bankDistance<18.0 then
sleep=0;drawMarkerAt(DevXCrimeConfig.Bank,210,55,75)
if bankDistance<1.8 then
help('~INPUT_CONTEXT~ vorbereiteten Bankraub starten')
if IsControlJustReleased(0,38) then
DevXTriggerCallback('crime:startRobbery',{},function(result)
if not result.ok then notify(result.error or 'Bankraub konnte nicht gestartet werden.');return end
robberyRunning=true
CreateThread(function()
timedAction('Tresor wird geöffnet …',result.duration)
DevXTriggerCallback('crime:finishRobbery',{},function(finish)
robberyRunning=false
if finish.ok then progress={drill=false,card=false,vehicle=false};notify(('Bankraub erfolgreich: %s $ und %s RP-XP.'):format(finish.reward,finish.xp))
else notify(finish.error or 'Bankraub fehlgeschlagen.') end
end)
end)
end)
end
end
end
end
end
Wait(sleep)
end
end)
AddEventHandler('onResourceStop',function(resourceName)
if resourceName~=GetCurrentResourceName() then return end
removeObjectiveProp();deletePeds(plannerPeds);deletePeds(warehousePeds);deleteProps(missionProps);deleteVehicles(missionVehicles);TriggerServerEvent('devx_core:server:returnToWorld');TriggerEvent('devx_ipl:client:restoreWorld', 'crime-stop')
end)