first commit
This commit is contained in:
@@ -0,0 +1,385 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user