local crowbarCoords = vec4(1699.67, 3775.30, 34.74, 207.22) local crowbarPed local crowbarBlip local crowbarBike local mackCoords = vec4(2366.60, 3154.91, 48.21, 17.84) local mackPed local mackBlip local gabeBlip local dekeCoords = vec4(2359.89, 3136.74, 48.21, 110.90) local dekePed local crowbarMission1Vehicle local crowbarMission1Blip local crowbarMission1ReturnActive = false local crowbarMission2Vehicle local crowbarMission2Blip -- ============================================================ -- Mack world setup -- ============================================================ local function createDeke() if DoesEntityExist(dekePed) then return end local model = `s_m_m_autoshop_02` lib.requestModel(model) dekePed = CreatePed( 0, model, dekeCoords.x, dekeCoords.y, dekeCoords.z - 1.0, dekeCoords.w, false, false ) SetPedDefaultComponentVariation(dekePed) SetEntityInvincible(dekePed, true) FreezeEntityPosition(dekePed, true) SetBlockingOfNonTemporaryEvents(dekePed, true) TaskStartScenarioInPlace( dekePed, 'WORLD_HUMAN_STAND_IMPATIENT', 0, true ) exports.ox_target:addLocalEntity(dekePed, { { name = 'how_progression_deke_shop', icon = 'fa-solid fa-toolbox', label = 'Talk to Deke', distance = 2.0, onSelect = function() exports.ox_inventory:openInventory( 'shop', { type = 'DekeTools', id = 1 } ) end } }) SetModelAsNoLongerNeeded(model) end local function createMack() createDeke() if DoesEntityExist(mackPed) then return end local model = `s_m_y_xmech_01` lib.requestModel(model) mackPed = CreatePed( 0, model, mackCoords.x, mackCoords.y, mackCoords.z - 1.0, mackCoords.w, false, false ) SetPedDefaultComponentVariation(mackPed) SetEntityInvincible(mackPed, true) FreezeEntityPosition(mackPed, true) SetBlockingOfNonTemporaryEvents(mackPed, true) TaskStartScenarioInPlace( mackPed, 'WORLD_HUMAN_CLIPBOARD', 0, true ) exports.ox_target:addLocalEntity(mackPed, { { name = 'how_progression_mack_talk', icon = 'fa-solid fa-comment', label = 'Talk to Mack', distance = 2.0, canInteract = function() return lib.callback.await( 'how_progression:server:canTalkToMack', false ) end, onSelect = function() local state = lib.callback.await( 'how_progression:server:getCrowbarMission4State', false ) if state == 'meet_mack' then lib.alertDialog({ header = 'Mack', content = "So you're Crowbar's new errand boy.\n\n" .. "He put in a good word for you. Well... as good a word " .. "as Crowbar gives anybody. Says you're about halfway trustworthy.\n\n" .. "You need that cash cleaned, go see Gabe. He's in the warehouse.\n\n" .. "And don't ask how it works. Gabe gets cranky when people ask questions.", centered = true }) TriggerServerEvent( 'how_progression:server:crowbarMission4TalkedToMack' ) else local mackState = lib.callback.await( 'how_progression:server:getMackMission1State', false ) if mackState and mackState.objective == 'available' then local answer = lib.alertDialog({ header = 'Mack', content = "Got a delivery for you. Guy named Vinny just opened up a little bike shop.\n\n" .. "Fixed a few Sanchezes for him. They're out back.\n\n" .. "Take 'em over. Apparently he's got customers waiting, which means he's called me about fourteen fucking times today.\n\n" .. "Try not to scratch 'em.\n\n" .. "Take these. It gets dangerous in my line of work.", centered = true, cancel = true, labels = { confirm = 'Take the job', cancel = 'Not now' } }) if answer == 'confirm' then TriggerServerEvent( 'how_progression:server:startMackMission1' ) end elseif mackState and mackState.objective ~= 'complete' then lib.notify({ title = 'Mack', description = "Finish what you're doing first.", type = 'inform' }) else lib.alertDialog({ header = 'Mack', content = "Crowbar says you're all right. That's good enough for me.", centered = true }) end end end } }) SetModelAsNoLongerNeeded(model) end -- ============================================================ -- Crowbar world setup -- ============================================================ local function createCrowbar() if DoesEntityExist(crowbarPed) then return end local model = `ig_clay` lib.requestModel(model) crowbarPed = CreatePed( 0, model, crowbarCoords.x, crowbarCoords.y, crowbarCoords.z - 1.0, crowbarCoords.w, false, false ) SetEntityInvincible(crowbarPed, true) FreezeEntityPosition(crowbarPed, true) SetBlockingOfNonTemporaryEvents(crowbarPed, true) TaskStartScenarioInPlace( crowbarPed, 'WORLD_HUMAN_SMOKING', 0, true ) exports.ox_target:addLocalEntity(crowbarPed, { { name = 'how_progression_crowbar_talk', icon = 'fa-solid fa-comment', label = 'Talk to Crowbar', distance = 2.0, canInteract = function() local active = lib.callback.await( 'how_progression:server:isCrowbarMissionActive', false ) return not active end, onSelect = function() local stage = lib.callback.await( 'how_progression:server:getCrowbarMission', false ) if stage == 1 then local result = lib.alertDialog({ header = 'Crowbar', content = "So you're the one looking for work.\n\n" .. "I don't know you, and I sure as hell don't trust you. " .. "So we're gonna start simple. You prove you can follow directions, " .. "maybe we'll talk about real work.\n\n" .. "Some creep owes me money. He's holed up at an abandoned service " .. "station outside town. God knows what that creep is doing in there.\n\n" .. "I don't want the money. I want his car. It's parked outside.\n\n" .. "I had a copy of the key made. Don't ask how. Take the car and " .. "bring it back here in one piece.\n\n" .. "Do that, and maybe you're worth knowing.", centered = true, cancel = true, labels = { confirm = "I'm in", cancel = "Not right now" } }) if result == 'confirm' then TriggerServerEvent( 'how_progression:server:startCrowbarMission1' ) end elseif stage == 2 then local result = lib.alertDialog({ header = 'Crowbar', content = "I always loved this car. Hope that creep's crying " .. "in a basement somewhere. Anyway...\n\n" .. "You did good. But don't get excited—I still don't trust you.\n\n" .. "So we're gonna try something a little harder. Another car. " .. "This time I didn't get you a key. You'll have to steal it yourself.\n\n" .. "Good news is, you get to keep this one.\n\n" .. "I'll give you the picks to get inside. You're gonna need a pair " .. "of snips to get it running. You Tool sells 'em.\n\n" .. "You in?", centered = true, cancel = true, labels = { confirm = "I'm in", cancel = "Not right now" } }) if result == 'confirm' then TriggerServerEvent( 'how_progression:server:startCrowbarMission2' ) end elseif stage == 3 then local result = lib.alertDialog({ header = 'Crowbar', content = "All right. Let's see how you handle yourself when things get loud.\n\n" .. "There's a 24/7 down in Harmony. Easy money.\n\n" .. "Use this. There's no magazine, so make it convincing. " .. "Hit the register and get out of there.\n\n" .. "You in?", centered = true, cancel = true, labels = { confirm = "I'm in", cancel = "Not right now" } }) if result == 'confirm' then TriggerServerEvent( 'how_progression:server:startCrowbarMission3' ) end elseif stage == 4 then local result = lib.alertDialog({ header = 'Crowbar', content = "That cash from the store? Don't spend it. It's marked.\n\n" .. "I know a guy who can help, but he doesn't deal with strangers. " .. "Go see Mack over at the scrapyard. I told him you were coming.\n\n" .. "Don't make me regret that.\n\n" .. "You in?", centered = true, cancel = true, labels = { confirm = "I'm in", cancel = "Not right now" } }) if result == 'confirm' then TriggerServerEvent( 'how_progression:server:startCrowbarMission4' ) end elseif stage == 5 then local result = lib.alertDialog({ header = 'Crowbar', content = "Now you got money you can actually spend. Good. " .. "Means I don't gotta keep supplying you.\n\n" .. "Next thing I got ain't a stick-up. You're picking up " .. "a package for me, and there's a chance somebody else wants it.\n\n" .. "Go buy yourself some ammo at the Ammu-Nation next door. " .. "Maybe a vest too, if you're the nervous type... or don't. " .. "I don't care. I just want my package.\n\n" .. "I'll make it worth your while.", centered = true, cancel = true, labels = { confirm = "I'm in", cancel = "Not right now" } }) if result == 'confirm' then TriggerServerEvent( 'how_progression:server:startCrowbarMission5' ) end elseif stage >= 6 then lib.alertDialog({ header = 'Crowbar', content = "You've done what I asked. For now, that's enough.\n\n" .. "I'll let you know when I've got something else.", centered = true }) else lib.alertDialog({ header = 'Crowbar', content = "You shouldn't be talking to me yet.", centered = true }) end end } }) SetModelAsNoLongerNeeded(model) end local function createCrowbarBlip() if DoesBlipExist(crowbarBlip) then return end crowbarBlip = AddBlipForCoord( crowbarCoords.x, crowbarCoords.y, crowbarCoords.z ) SetBlipSprite(crowbarBlip, 280) SetBlipScale(crowbarBlip, 0.75) SetBlipAsShortRange(crowbarBlip, false) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Crowbar') EndTextCommandSetBlipName(crowbarBlip) end local function createCrowbarBike() if DoesEntityExist(crowbarBike) then return end local model = `daemon` lib.requestModel(model) crowbarBike = CreateVehicle( model, 1699.70, 3771.63, 34.53, 258.21, false, false ) SetEntityAsMissionEntity(crowbarBike, true, true) SetVehicleOnGroundProperly(crowbarBike) SetVehicleDoorsLocked(crowbarBike, 2) SetEntityInvincible(crowbarBike, true) FreezeEntityPosition(crowbarBike, true) SetModelAsNoLongerNeeded(model) end local function unlockCrowbar() createCrowbar() createCrowbarBike() createCrowbarBlip() print('[how_progression] Crowbar unlocked') end RegisterNetEvent( 'how_progression:client:unlockCrowbar', unlockCrowbar ) -- Forward declarations for progression-driven world state. local createMackBlip local unlockVinnyWorld -- Restore persistent progression-driven world state after character login. RegisterNetEvent( 'how_progression:client:syncProgression', function(progression) if not progression then return end -- Stage 0 means the contact has not been discovered. if (progression.crowbar_stage or 0) >= 1 then unlockCrowbar() end -- Mack becomes a persistent known contact once his progression begins. if (progression.mack_stage or 0) >= 1 then createMack() createMackBlip() end if progression.vinny_fence_unlocked == true or tonumber(progression.vinny_fence_unlocked) == 1 then unlockVinnyWorld() end -- Future persistent contacts are restored here. -- Tina, Cosmic Dave, etc. will use their appropriate progression -- state as they are added. end ) -- ============================================================ -- Mack Mission 1: Easy Rider -- ============================================================ local mackMission1BikeSpawns = { vec4(2360.90, 3114.91, 48.21, 239.14), vec4(2360.28, 3113.93, 48.21, 245.54), vec4(2359.64, 3112.59, 48.21, 265.43), vec4(2359.45, 3111.15, 48.21, 246.34), vec4(2361.34, 3117.44, 48.21, 234.06) } local vinnyMissionDrop = vec3(1373.86, 3615.83, 34.89) local vinnyFenceDrop = vec4(1356.79, 3617.37, 34.88, 105.38) local vinnyCoords = vec4(1362.57, 3602.49, 34.96, 236.75) local mackMission1DetourCoords = vec3(252.87, 2814.15, 43.19) local mackMission1TrashCarCoords = vec4(285.10, 2827.15, 42.89, 116.30) local mackMission1Bikes = {} local mackMission1TrashCar local mackMission1Blip local mackMission1Active = false local mackMission1Objective local mackMission1Delivered = {} local mackMission1DetourSent = false local mackMission1TrashReported = false local mackMission1Owner = false local mackMission1BikeCount = 1 local mackMission1HeadingToVinny = false local mackMission1DetourExplained = false local mackMission1DeliveryMarkerActive = false local vinnyPed local vinnyBlip local vinnyFenceUnlocked = false local vinnyDisplayBikes = {} local vinnyDisplayPositions = { vec4(1368.39, 3594.54, 34.34, 237.54), vec4(1366.95, 3598.24, 34.37, 244.92), vec4(1368.76, 3592.05, 34.37, 241.99), vec4(1357.67, 3601.45, 34.44, 194.85), vec4(1364.94, 3603.77, 34.33, 195.30) } local function clearMackMission1Blip() if DoesBlipExist(mackMission1Blip) then RemoveBlip(mackMission1Blip) end mackMission1Blip = nil end local function setMackMission1Route(coords, name) clearMackMission1Blip() mackMission1Blip = AddBlipForCoord(coords.x, coords.y, coords.z) SetBlipSprite(mackMission1Blip, 1) SetBlipColour(mackMission1Blip, 5) SetBlipRoute(mackMission1Blip, true) SetBlipRouteColour(mackMission1Blip, 5) BeginTextCommandSetBlipName('STRING') AddTextComponentString(name) EndTextCommandSetBlipName(mackMission1Blip) SetNewWaypoint(coords.x, coords.y) end local function spawnMackMission1Bikes() if #mackMission1Bikes > 0 then return end local model = `sanchez` lib.requestModel(model) for i = 1, mackMission1BikeCount do local coords = mackMission1BikeSpawns[i] local bike = CreateVehicle( model, coords.x, coords.y, coords.z, coords.w, true, true ) SetEntityAsMissionEntity(bike, true, true) SetVehicleOnGroundProperly(bike) SetVehicleDoorsLocked(bike, 1) SetVehicleEngineOn(bike, false, true, true) Entity(bike).state:set( 'howMackMission1Bike', i, true ) mackMission1Bikes[i] = bike local netId = NetworkGetNetworkIdFromEntity(bike) TriggerServerEvent( 'how_progression:server:mackMission1BikeSpawned', i, netId ) end SetModelAsNoLongerNeeded(model) end local function spawnMackMission1TrashCar() if DoesEntityExist(mackMission1TrashCar) then return end local model = `superd` lib.requestModel(model) mackMission1TrashCar = CreateVehicle( model, mackMission1TrashCarCoords.x, mackMission1TrashCarCoords.y, mackMission1TrashCarCoords.z, mackMission1TrashCarCoords.w, true, true ) SetEntityAsMissionEntity( mackMission1TrashCar, true, true ) SetVehicleOnGroundProperly(mackMission1TrashCar) SetVehicleDoorsLocked(mackMission1TrashCar, 2) SetVehicleEngineOn( mackMission1TrashCar, false, true, true ) SetModelAsNoLongerNeeded(model) end local function createVinnyDisplayBikes() lib.requestModel(`sanchez`) local price = exports.qbx_core:GetVehiclesByName().sanchez.price for i, coords in ipairs(vinnyDisplayPositions) do if not DoesEntityExist(vinnyDisplayBikes[i]) then local bike = CreateVehicle( `sanchez`, coords.x, coords.y, coords.z, coords.w, false, false ) if DoesEntityExist(bike) then SetEntityAsMissionEntity(bike, true, true) SetVehicleOnGroundProperly(bike) SetVehicleDoorsLocked(bike, 2) SetEntityInvincible(bike, true) FreezeEntityPosition(bike, true) exports.ox_target:addLocalEntity(bike, { { name = 'how_progression_vinny_buy_' .. i, icon = 'fa-solid fa-motorcycle', label = ('Buy Sanchez ($%s)'):format( lib.math.groupdigits(price) ), distance = 2.0, onSelect = function() TriggerServerEvent( 'qbx_vehicleshop:server:buyShowroomVehicle', 'sanchez' ) end } }) vinnyDisplayBikes[i] = bike end end end SetModelAsNoLongerNeeded(`sanchez`) end local function createVinny() if DoesEntityExist(vinnyPed) then createVinnyDisplayBikes() return end -- Generic salesman/businessman. Easy to change after we see him. local model = `a_m_m_business_01` lib.requestModel(model) vinnyPed = CreatePed( 0, model, vinnyCoords.x, vinnyCoords.y, vinnyCoords.z - 1.0, vinnyCoords.w, false, false ) SetPedDefaultComponentVariation(vinnyPed) SetEntityAsMissionEntity(vinnyPed, true, true) SetEntityInvincible(vinnyPed, true) FreezeEntityPosition(vinnyPed, true) SetBlockingOfNonTemporaryEvents(vinnyPed, true) TaskStartScenarioInPlace( vinnyPed, 'WORLD_HUMAN_STAND_IMPATIENT', 0, true ) exports.ox_target:addLocalEntity(vinnyPed, { { name = 'how_progression_vinny_talk', icon = 'fa-solid fa-comment', label = 'Talk to Vinny', distance = 2.0, canInteract = function() return mackMission1Objective == 'talk_vinny' end, onSelect = function() lib.alertDialog({ header = 'Vinny', content = "Yeah, thanks, thanks. Now get outta here. " .. "I see a sucker ready to buy.\n\n" .. "Give me your number and I'll text ya.\n\n" .. "I promise you're gonna like this.", centered = true }) TriggerServerEvent( 'how_progression:server:completeMackMission1' ) end } }) SetModelAsNoLongerNeeded(model) createVinnyDisplayBikes() end local function createVinnyBlip() if DoesBlipExist(vinnyBlip) then return end vinnyBlip = AddBlipForCoord( vinnyCoords.x, vinnyCoords.y, vinnyCoords.z ) SetBlipSprite(vinnyBlip, 226) SetBlipScale(vinnyBlip, 0.7) SetBlipAsShortRange(vinnyBlip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Vinny') EndTextCommandSetBlipName(vinnyBlip) end unlockVinnyWorld = function() vinnyFenceUnlocked = true mackMission1DeliveryMarkerActive = false createVinny() createVinnyBlip() end AddEventHandler('QBCore:Client:OnPlayerUnload', function() vinnyFenceUnlocked = false mackMission1DeliveryMarkerActive = false if DoesEntityExist(vinnyPed) then exports.ox_target:removeLocalEntity(vinnyPed, 'how_progression_vinny_talk') DeleteEntity(vinnyPed) end vinnyPed = nil if DoesBlipExist(vinnyBlip) then RemoveBlip(vinnyBlip) end vinnyBlip = nil for i, bike in ipairs(vinnyDisplayBikes) do if DoesEntityExist(bike) then exports.ox_target:removeLocalEntity( bike, 'how_progression_vinny_buy_' .. i ) DeleteVehicle(bike) end end vinnyDisplayBikes = {} end) RegisterNetEvent( 'how_progression:client:startMackMission1', function(isOwner, bikeCount) mackMission1Active = true mackMission1Objective = 'leave_scrapyard' mackMission1Delivered = {} mackMission1DetourSent = false mackMission1TrashReported = false mackMission1HeadingToVinny = false mackMission1DetourExplained = false mackMission1DeliveryMarkerActive = false mackMission1Owner = isOwner == true mackMission1BikeCount = math.min( math.max(tonumber(bikeCount) or 1, 1), 5 ) if mackMission1Owner then spawnMackMission1Bikes() end createVinny() setMackMission1Route( vec3( mackMission1BikeSpawns[1].x, mackMission1BikeSpawns[1].y, mackMission1BikeSpawns[1].z ), 'Pick Up the Bikes' ) lib.notify({ title = 'Easy Rider', description = ( 'Pick up the Sanchez%s behind Mack. (%d total)' ):format( mackMission1BikeCount == 1 and '' or 'es', mackMission1BikeCount ), type = 'inform' }) end ) RegisterNetEvent( 'how_progression:client:mackMission1TrashCar', function(isOwner) if not mackMission1Active then return end mackMission1Objective = 'trash_car' if isOwner == true then spawnMackMission1TrashCar() end setMackMission1Route( mackMission1DetourCoords, "Mack's Detour" ) lib.notify({ title = 'Mack', description = "Read Mack's message.", type = 'inform' }) end ) RegisterNetEvent( 'how_progression:client:mackMission1DetourExplained', function() if not mackMission1Active then return end mackMission1DetourExplained = true local targetCoords = mackMission1TrashCarCoords if DoesEntityExist(mackMission1TrashCar) then targetCoords = GetEntityCoords(mackMission1TrashCar) end setMackMission1Route(targetCoords, "Mack's Target") lib.notify({ title = 'Mack', description = "Read Mack's message.", type = 'inform' }) end ) RegisterNetEvent( 'how_progression:client:mackMission1DeliverBikes', function(deliveredCount, bikeCount) if not mackMission1Active then return end mackMission1Objective = 'deliver_bikes' mackMission1DeliveryMarkerActive = true mackMission1BikeCount = tonumber(bikeCount) or mackMission1BikeCount setMackMission1Route( vinnyMissionDrop, 'Deliver the Bikes' ) lib.notify({ title = 'Easy Rider', description = ( 'Deliver the Sanchez%s to Vinny. (%d/%d)' ):format( mackMission1BikeCount == 1 and '' or 'es', deliveredCount or 0, mackMission1BikeCount ), type = 'inform' }) end ) RegisterNetEvent( 'how_progression:client:mackMission1DeliveryCount', function(count, bikeCount) mackMission1BikeCount = tonumber(bikeCount) or mackMission1BikeCount lib.notify({ title = 'Easy Rider', description = ( 'Sanchez%s delivered: %d/%d' ):format( mackMission1BikeCount == 1 and '' or 'es', count, mackMission1BikeCount ), type = 'success' }) end ) RegisterNetEvent( 'how_progression:client:mackMission1TalkVinny', function() mackMission1Objective = 'talk_vinny' mackMission1DeliveryMarkerActive = false clearMackMission1Blip() setMackMission1Route( vec3( vinnyCoords.x, vinnyCoords.y, vinnyCoords.z ), 'Vinny' ) lib.notify({ title = 'Easy Rider', description = 'Talk to Vinny.', type = 'inform' }) end ) RegisterNetEvent( 'how_progression:client:mackMission1Complete', function() mackMission1Active = false mackMission1Objective = nil mackMission1Owner = false mackMission1DeliveryMarkerActive = false ClearGpsPlayerWaypoint() clearMackMission1Blip() lib.notify({ title = 'Easy Rider', description = 'Job complete.', type = 'success' }) end ) RegisterNetEvent( 'how_progression:client:mackReward', function(repReward, newRep, _, cashReward) lib.notify({ title = 'Mack', description = ( '$%d | +%d REP | Mack REP: %d' ):format( cashReward or 0, repReward or 0, newRep or 0 ), type = 'success' }) end ) RegisterNetEvent( 'how_progression:client:unlockVinnyFence', function() unlockVinnyWorld() lib.notify({ title = 'Sanchez Fence Unlocked', description = 'Vinny buys stolen Sanchezes around back.', type = 'success' }) end ) RegisterNetEvent('how_progression:client:leaveFenceBike', function(netId) local ped = PlayerPedId() local vehicle = GetVehiclePedIsIn(ped, false) if vehicle ~= 0 and NetworkGetNetworkIdFromEntity(vehicle) == netId then TaskLeaveVehicle(ped, vehicle, 0) end end) CreateThread(function() local lastAttempt = 0 while true do Wait(vinnyFenceUnlocked and 150 or 1000) if vinnyFenceUnlocked and GetGameTimer() - lastAttempt >= 5000 then local ped = PlayerPedId() local vehicle = GetVehiclePedIsIn(ped, false) if vehicle ~= 0 and GetPedInVehicleSeat(vehicle, -1) == ped and GetEntityModel(vehicle) == `sanchez` and #(GetEntityCoords(vehicle) - vinnyFenceDrop.xyz) <= 2.5 then lastAttempt = GetGameTimer() local gameDay = GetClockYear() * 10000 + GetClockMonth() * 100 + GetClockDayOfMonth() local success, message = lib.callback.await( 'how_progression:server:fenceSanchez', false, NetworkGetNetworkIdFromEntity(vehicle), gameDay ) lib.notify({ title = 'Vinny', description = message or 'Vinny could not take that bike.', type = success and 'success' or 'error' }) end end end end) -- Draw Vinny's fence marker and the mission-bike delivery circle every frame. CreateThread(function() while true do Wait(0) if vinnyFenceUnlocked then DrawMarker( 1, vinnyFenceDrop.x, vinnyFenceDrop.y, vinnyFenceDrop.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, vinnyFenceDrop.w, 3.0, 3.0, 0.5, 255, 255, 255, 150, false, false, 2, false, nil, nil, false ) end if mackMission1DeliveryMarkerActive and mackMission1Objective == 'deliver_bikes' then DrawMarker( 1, vinnyMissionDrop.x, vinnyMissionDrop.y, vinnyMissionDrop.z - 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 4.5, 4.5, 1.0, 255, 255, 255, 160, false, false, 2, false, nil, nil, false ) end end end) -- Mission monitoring. CreateThread(function() while true do if not mackMission1Active then Wait(1000) else Wait(100) local ped = PlayerPedId() local playerCoords = GetEntityCoords(ped) -- Riding a mission Sanchez makes Vinny the main destination. if mackMission1Objective == 'leave_scrapyard' and not mackMission1HeadingToVinny and IsPedInAnyVehicle(ped, false) then local vehicle = GetVehiclePedIsIn(ped, false) if GetPedInVehicleSeat(vehicle, -1) == ped then local missionBike = Entity(vehicle).state.howMackMission1Bike if missionBike then mackMission1HeadingToVinny = true setMackMission1Route( vinnyMissionDrop, 'Deliver the Bikes' ) lib.notify({ title = 'Easy Rider', description = 'Deliver the bike to Vinny.', type = 'inform' }) end end end -- Mack interrupts once the crew is underway. if mackMission1Objective == 'leave_scrapyard' and not mackMission1DetourSent then local distance = #(playerCoords - vec3( mackMission1BikeSpawns[1].x, mackMission1BikeSpawns[1].y, mackMission1BikeSpawns[1].z )) if distance >= 175.0 then mackMission1DetourSent = true TriggerServerEvent( 'how_progression:server:mackMission1Detour' ) end end -- Reaching Mack's address triggers the actual explanation. if mackMission1Objective == 'trash_car' and not mackMission1DetourExplained then local distance = #(playerCoords - mackMission1DetourCoords) if distance <= 35.0 then mackMission1DetourExplained = true TriggerServerEvent( 'how_progression:server:mackMission1AtDetour' ) end end -- The car must now be genuinely trashed. -- -- Body health must be heavily reduced AND the engine must -- have taken meaningful damage. A few bumps won't do it. if mackMission1Owner and mackMission1Objective == 'trash_car' and mackMission1DetourExplained and DoesEntityExist(mackMission1TrashCar) and not mackMission1TrashReported then local bodyHealth = GetVehicleBodyHealth(mackMission1TrashCar) local engineHealth = GetVehicleEngineHealth(mackMission1TrashCar) if bodyHealth <= 250.0 and engineHealth <= 650.0 then mackMission1TrashReported = true TriggerServerEvent( 'how_progression:server:mackMission1CarTrashed' ) end end if mackMission1DeliveryMarkerActive and mackMission1Objective == 'deliver_bikes' then -- Owner is authoritative for mission-bike delivery. if mackMission1Owner and IsPedInAnyVehicle(ped, false) then local vehicle = GetVehiclePedIsIn(ped, false) if GetPedInVehicleSeat(vehicle, -1) == ped then local bikeIndex = Entity(vehicle).state.howMackMission1Bike if bikeIndex and not mackMission1Delivered[bikeIndex] then local vehicleCoords = GetEntityCoords(vehicle) local distance = #(vehicleCoords - vinnyMissionDrop) if distance <= 3.0 then mackMission1Delivered[bikeIndex] = true TaskLeaveVehicle( ped, vehicle, 0 ) local timeout = GetGameTimer() + 3000 while IsPedInVehicle( ped, vehicle, false ) and GetGameTimer() < timeout do Wait(50) end Wait(500) if DoesEntityExist(vehicle) then SetEntityAsMissionEntity( vehicle, true, true ) DeleteVehicle(vehicle) end TriggerServerEvent( 'how_progression:server:mackMission1BikeDelivered', bikeIndex ) end end end end end end end end) -- ============================================================ -- Crowbar Mission 4: Wash the Cash -- ============================================================ local gabeCoords = vec3(2340.96, 3126.11, 48.21) createMackBlip = function() if DoesBlipExist(mackBlip) then return end mackBlip = AddBlipForCoord( mackCoords.x, mackCoords.y, mackCoords.z ) SetBlipSprite(mackBlip, 280) SetBlipScale(mackBlip, 0.75) SetBlipAsShortRange(mackBlip, false) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Mack') EndTextCommandSetBlipName(mackBlip) end local function createGabeBlip() if DoesBlipExist(gabeBlip) then return end gabeBlip = AddBlipForCoord( gabeCoords.x, gabeCoords.y, gabeCoords.z ) SetBlipSprite(gabeBlip, 500) SetBlipDisplay(gabeBlip, 4) SetBlipScale(gabeBlip, 0.7) SetBlipAsShortRange(gabeBlip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Money Laundering') EndTextCommandSetBlipName(gabeBlip) end RegisterNetEvent( 'how_progression:client:startCrowbarMission4', function() createMackBlip() SetNewWaypoint(mackCoords.x, mackCoords.y) lib.notify({ title = 'Wash the Cash', description = 'Go meet Mack at the scrapyard.', type = 'inform' }) end ) RegisterNetEvent( 'how_progression:client:crowbarMission4Mack', function() ClearGpsPlayerWaypoint() createMackBlip() createGabeBlip() SetNewWaypoint(gabeCoords.x, gabeCoords.y) end ) RegisterNetEvent( 'how_progression:client:crowbarMission4Gabe', function() ClearGpsPlayerWaypoint() createGabeBlip() end ) RegisterNetEvent( 'how_progression:client:unlockMack', function(setRoute) createMack() createMackBlip() if setRoute then SetNewWaypoint(mackCoords.x, mackCoords.y) end end ) RegisterNetEvent( 'how_progression:client:unlockGabe', function(setRoute) createGabeBlip() if setRoute then SetNewWaypoint(gabeCoords.x, gabeCoords.y) end end ) RegisterNetEvent( 'how_progression:client:cleanupCrowbarMission4', function() ClearGpsPlayerWaypoint() createMackBlip() createGabeBlip() end ) -- ============================================================ -- Crowbar Mission 5: Buy In -- ============================================================ local crowbarMission5Drop = vec3(1537.56, 1700.49, 109.68) local crowbarMission5DropBlip local crowbarMission5ReturnBlip local crowbarMission5Active = false local crowbarMission5CanReturn = false local crowbarMission5StashId local crowbarMission5Crate local function clearCrowbarMission5Blips() if DoesBlipExist(crowbarMission5DropBlip) then RemoveBlip(crowbarMission5DropBlip) crowbarMission5DropBlip = nil end if DoesBlipExist(crowbarMission5ReturnBlip) then RemoveBlip(crowbarMission5ReturnBlip) crowbarMission5ReturnBlip = nil end end local function createCrowbarMission5DropBlip() if DoesBlipExist(crowbarMission5DropBlip) then return end crowbarMission5DropBlip = AddBlipForCoord( crowbarMission5Drop.x, crowbarMission5Drop.y, crowbarMission5Drop.z ) SetBlipSprite(crowbarMission5DropBlip, 478) SetBlipScale(crowbarMission5DropBlip, 0.75) SetBlipRoute(crowbarMission5DropBlip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Package Pickup') EndTextCommandSetBlipName(crowbarMission5DropBlip) end local function createCrowbarMission5ReturnBlip() if DoesBlipExist(crowbarMission5ReturnBlip) then return end crowbarMission5ReturnBlip = AddBlipForCoord( crowbarCoords.x, crowbarCoords.y, crowbarCoords.z ) SetBlipSprite(crowbarMission5ReturnBlip, 280) SetBlipScale(crowbarMission5ReturnBlip, 0.75) SetBlipRoute(crowbarMission5ReturnBlip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Crowbar') EndTextCommandSetBlipName(crowbarMission5ReturnBlip) end local function armCrowbarMission5Ambusher(ped) SetEntityAsMissionEntity(ped, true, true) SetPedRelationshipGroupHash( ped, `HOW_CROWBAR_AMBUSH` ) GiveWeaponToPed( ped, `WEAPON_PISTOL`, 90, false, true ) SetPedAccuracy(ped, 18) SetPedCombatAbility(ped, 1) SetPedCombatRange(ped, 1) SetPedCombatMovement(ped, 2) SetPedKeepTask(ped, true) end local crowbarMission5Ambushers = {} local crowbarMission5EscapeActive = false local crowbarMission5EscapeReported = false local function attackCrowbarMission5Crew(ped) TaskCombatHatedTargetsAroundPed( ped, 150.0, 0 ) end local function createCrowbarMission5AmbushVehicle(data) local vehicleModel = joaat(data.vehicle) lib.requestModel(vehicleModel) local vehicle = CreateVehicle( vehicleModel, data.spawn.x, data.spawn.y, data.spawn.z, data.spawn.w, true, true ) if not DoesEntityExist(vehicle) then print( '[how_progression] Failed to create M5 ambush vehicle: ' .. data.vehicle ) return end SetEntityAsMissionEntity(vehicle, true, true) SetVehicleOnGroundProperly(vehicle) SetVehicleEngineOn(vehicle, true, true, false) local occupants = {} for seat = -1, 1 do local model = data.peds[seat + 2] lib.requestModel(model) local ped = CreatePedInsideVehicle( vehicle, 4, model, seat, true, true ) if DoesEntityExist(ped) then armCrowbarMission5Ambusher(ped) occupants[#occupants + 1] = ped crowbarMission5Ambushers[#crowbarMission5Ambushers + 1] = ped end SetModelAsNoLongerNeeded(model) end local driver = occupants[1] if not DoesEntityExist(driver) then DeleteEntity(vehicle) return end SetDriverAbility(driver, 1.0) SetDriverAggressiveness(driver, 1.0) CreateThread(function() local target = PlayerPedId() local lastTask = 0 local onFootAttack = false while crowbarMission5Active and crowbarMission5EscapeActive and DoesEntityExist(vehicle) and DoesEntityExist(driver) and not IsEntityDead(driver) and DoesEntityExist(target) and not IsEntityDead(target) do local vehicleCoords = GetEntityCoords(vehicle) local targetCoords = GetEntityCoords(target) local distance = #(vehicleCoords - targetCoords) -- -- Stay in the truck and pursue while the carrier is -- moving away or still some distance from the Lost. -- if distance > 30.0 and not onFootAttack then if GetGameTimer() - lastTask > 2500 then TaskVehicleChase(driver, target) SetDriveTaskDrivingStyle( driver, 786603 ) SetDriveTaskCruiseSpeed( driver, 35.0 ) lastTask = GetGameTimer() end -- -- Once they actually catch the carrier, pile out and fight. -- elseif distance <= 30.0 and not onFootAttack then onFootAttack = true BringVehicleToHalt( vehicle, 4.0, 1000, false ) Wait(750) for _, ped in ipairs(occupants) do if DoesEntityExist(ped) and not IsEntityDead(ped) then TaskLeaveVehicle( ped, vehicle, 256 ) end end Wait(1200) for _, ped in ipairs(occupants) do if DoesEntityExist(ped) and not IsEntityDead(ped) then TaskCombatPed( ped, target, 0, 16 ) SetPedKeepTask(ped, true) end end -- -- If the carrier runs after the Lost have dismounted, -- keep the surviving attackers actively hunting them. -- elseif onFootAttack then for _, ped in ipairs(occupants) do if DoesEntityExist(ped) and not IsEntityDead(ped) and not IsPedInCombat(ped, target) then TaskCombatPed( ped, target, 0, 16 ) SetPedKeepTask(ped, true) end end end Wait(500) end end) SetModelAsNoLongerNeeded(vehicleModel) end local function spawnCrowbarMission5Ambush() AddRelationshipGroup('HOW_CROWBAR_AMBUSH') SetRelationshipBetweenGroups( 5, `HOW_CROWBAR_AMBUSH`, `PLAYER` ) SetRelationshipBetweenGroups( 5, `PLAYER`, `HOW_CROWBAR_AMBUSH` ) lib.notify({ title = 'Buy In', description = "Looks like somebody else wanted Crowbar's package.", type = 'error' }) CreateThread(function() -- Lead truck createCrowbarMission5AmbushVehicle({ vehicle = 'rebel', spawn = vec4( 1464.0, 1760.0, 108.0, 130.0 ), destination = vec3( 1521.0, 1715.0, 109.0 ), peds = { `g_m_y_lost_01`, `g_m_y_lost_02`, `g_m_y_lost_03` } }) -- Second truck follows the same reliable approach. createCrowbarMission5AmbushVehicle({ vehicle = 'bodhi2', spawn = vec4( 1448.0, 1774.0, 108.0, 130.0 ), destination = vec3( 1513.0, 1723.0, 109.0 ), peds = { `g_m_y_lost_02`, `g_m_y_lost_03`, `g_m_y_lost_01` } }) end) end RegisterNetEvent( 'how_progression:client:startCrowbarMission5', function(stashId) crowbarMission5Active = true crowbarMission5CanReturn = false crowbarMission5StashId = stashId clearCrowbarMission5Blips() createCrowbarMission5DropBlip() SetNewWaypoint( crowbarMission5Drop.x, crowbarMission5Drop.y ) lib.notify({ title = 'Buy In', description = 'Find the package at the desert shed.', type = 'inform' }) end ) RegisterNetEvent( 'how_progression:client:crowbarMission5PackageTaken', function(spawnAmbush) crowbarMission5CanReturn = false if DoesBlipExist(crowbarMission5DropBlip) then RemoveBlip(crowbarMission5DropBlip) crowbarMission5DropBlip = nil end ClearGpsPlayerWaypoint() if spawnAmbush then crowbarMission5Ambushers = {} crowbarMission5EscapeActive = true crowbarMission5EscapeReported = false spawnCrowbarMission5Ambush() CreateThread(function() -- -- Give both ambush vehicles time to spawn before -- evaluating whether everybody is dead. -- Wait(3000) local clearSince = nil while crowbarMission5Active and crowbarMission5EscapeActive and not crowbarMission5EscapeReported do local playerPed = PlayerPedId() local playerCoords = GetEntityCoords(playerPed) local living = 0 local allFarEnough = true for _, ped in ipairs(crowbarMission5Ambushers) do if DoesEntityExist(ped) and not IsEntityDead(ped) then living = living + 1 local distance = #(GetEntityCoords(ped) - playerCoords) if distance < 250.0 then allFarEnough = false end end end -- -- Killing every ambusher resolves the escape -- immediately. -- if living == 0 and #crowbarMission5Ambushers > 0 then crowbarMission5EscapeReported = true TriggerServerEvent( 'how_progression:server:crowbarMission5Escaped' ) break end -- -- Otherwise every surviving pursuer must remain -- at least 250m away for 20 continuous seconds. -- if living > 0 and allFarEnough then if not clearSince then clearSince = GetGameTimer() elseif GetGameTimer() - clearSince >= 20000 then crowbarMission5EscapeReported = true TriggerServerEvent( 'how_progression:server:crowbarMission5Escaped' ) break end else clearSince = nil end Wait(500) end end) end end ) RegisterNetEvent( 'how_progression:client:crowbarMission5Escaped', function() crowbarMission5EscapeActive = false crowbarMission5CanReturn = true lib.notify({ title = 'Buy In', description = 'You lost the pursuers.', type = 'success' }) createCrowbarMission5ReturnBlip() SetNewWaypoint( crowbarCoords.x, crowbarCoords.y ) end ) RegisterNetEvent( 'how_progression:client:cleanupCrowbarMission5', function() crowbarMission5Active = false crowbarMission5CanReturn = false crowbarMission5EscapeActive = false crowbarMission5EscapeReported = false crowbarMission5StashId = nil crowbarMission5Ambushers = {} ClearGpsPlayerWaypoint() clearCrowbarMission5Blips() end ) RegisterNetEvent( 'how_progression:client:crowbarMission5Complete', function() lib.alertDialog({ header = 'Crowbar', content = "You got it here. That's all that matters.", centered = true }) end ) local function createCrowbarMission5Crate() if DoesEntityExist(crowbarMission5Crate) then return end local model = `prop_box_wood02a_pu` lib.requestModel(model) -- Visually verified placement for the package crate. local objectZ = 108.6397 crowbarMission5Crate = CreateObject( model, crowbarMission5Drop.x, crowbarMission5Drop.y, objectZ, false, false, false ) SetEntityHeading(crowbarMission5Crate, 56.26) FreezeEntityPosition(crowbarMission5Crate, true) SetEntityAsMissionEntity( crowbarMission5Crate, true, true ) exports.ox_target:addLocalEntity( crowbarMission5Crate, { { name = 'how_progression_crowbar_package', icon = 'fa-solid fa-box', label = 'Search Container', distance = 2.5, canInteract = function() return crowbarMission5Active and not crowbarMission5CanReturn and crowbarMission5StashId ~= nil end, onSelect = function() exports.ox_inventory:openInventory( 'stash', crowbarMission5StashId ) end } } ) SetModelAsNoLongerNeeded(model) end CreateThread(function() Wait(2000) createCrowbarMission5Crate() end) exports.ox_target:addSphereZone({ coords = vec3( crowbarCoords.x, crowbarCoords.y, crowbarCoords.z ), radius = 2.0, debug = false, options = { { name = 'how_progression_crowbar_package_return', icon = 'fa-solid fa-box', label = 'Give Crowbar the Package', canInteract = function() return crowbarMission5Active and crowbarMission5CanReturn end, onSelect = function() TriggerServerEvent( 'how_progression:server:completeCrowbarMission5' ) end } } }) -- ============================================================ -- Crowbar Mission 1 -- ============================================================ RegisterNetEvent( 'how_progression:client:startCrowbarMission1', function(netId) crowbarMission1Vehicle = netId crowbarMission1ReturnActive = false if DoesBlipExist(crowbarMission1Blip) then RemoveBlip(crowbarMission1Blip) crowbarMission1Blip = nil end crowbarMission1Blip = AddBlipForCoord( 911.40, 3589.50, 33.25 ) SetBlipSprite(crowbarMission1Blip, 225) SetBlipScale(crowbarMission1Blip, 0.85) SetBlipRoute(crowbarMission1Blip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString("Crowbar's Target") EndTextCommandSetBlipName(crowbarMission1Blip) lib.notify({ title = 'Crowbar', description = "Take the car and bring it back to Crowbar.", type = 'inform', duration = 5000 }) end ) RegisterNetEvent( 'how_progression:client:crowbarMission1Return', function() if not crowbarMission1Vehicle then return end crowbarMission1ReturnActive = true if DoesBlipExist(crowbarMission1Blip) then RemoveBlip(crowbarMission1Blip) crowbarMission1Blip = nil end crowbarMission1Blip = AddBlipForCoord( 1705.50, 3771.76, 34.49 ) SetBlipSprite(crowbarMission1Blip, 1) SetBlipScale(crowbarMission1Blip, 0.85) SetBlipRoute(crowbarMission1Blip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString("Crowbar's Drop-Off") EndTextCommandSetBlipName(crowbarMission1Blip) lib.notify({ title = 'Crowbar', description = 'Good. Bring it back to me.', type = 'inform', duration = 5000 }) end ) -- Mission 1 state transition: -- Once the player starts the target vehicle, remove the target -- blip and route them back to Crowbar's drop-off. CreateThread(function() while true do Wait(500) if crowbarMission1Vehicle and not crowbarMission1ReturnActive and NetworkDoesEntityExistWithNetworkId( crowbarMission1Vehicle ) then local vehicle = NetToVeh(crowbarMission1Vehicle) if DoesEntityExist(vehicle) then local playerPed = PlayerPedId() if GetPedInVehicleSeat( vehicle, -1 ) == playerPed and GetIsVehicleEngineRunning(vehicle) then TriggerServerEvent( 'how_progression:server:crowbarMission1VehicleAcquired' ) end end end end end) -- Mission 1 completion / return detection. CreateThread(function() while true do Wait(500) if crowbarMission1Vehicle and crowbarMission1ReturnActive then local vehicle = NetToVeh(crowbarMission1Vehicle) if DoesEntityExist(vehicle) then local playerPed = PlayerPedId() local vehicleCoords = GetEntityCoords(vehicle) local returnCoords = vector3( 1705.50, 3771.76, 34.49 ) if #(vehicleCoords - returnCoords) < 8.0 and GetPedInVehicleSeat( vehicle, -1 ) == playerPed then -- Prevent this from firing repeatedly while -- the player is getting out. crowbarMission1ReturnActive = false if DoesBlipExist(crowbarMission1Blip) then RemoveBlip(crowbarMission1Blip) crowbarMission1Blip = nil end -- Shut the vehicle down and have the player exit. SetVehicleEngineOn(vehicle, false, true, true) SetVehicleHandbrake(vehicle, true) TaskLeaveVehicle( playerPed, vehicle, 0 ) lib.notify({ title = 'Crowbar', description = 'Good. Leave the car here.', type = 'inform', duration = 3000 }) -- Wait until the player has actually exited -- before asking the server to delete the vehicle. CreateThread(function() local timeout = GetGameTimer() + 10000 while DoesEntityExist(vehicle) and GetPedInVehicleSeat( vehicle, -1 ) == playerPed and GetGameTimer() < timeout do Wait(250) end if DoesEntityExist(vehicle) then TriggerServerEvent( 'how_progression:server:completeCrowbarMission1', crowbarMission1Vehicle ) end crowbarMission1Vehicle = nil end) end end end end end) -- Visible return marker. CreateThread(function() while true do Wait(0) if crowbarMission1ReturnActive then DrawMarker( 1, 1705.50, 3771.76, 33.49, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.0, 3.0, 0.5, 255, 255, 255, 150, false, false, 2, false, nil, nil, false ) end end end) -- ============================================================ -- Crowbar Mission 2 -- ============================================================ RegisterNetEvent( 'how_progression:client:startCrowbarMission2', function(netId) crowbarMission2Vehicle = netId crowbarMission2Stolen = false if DoesBlipExist(crowbarMission2Blip) then RemoveBlip(crowbarMission2Blip) crowbarMission2Blip = nil end crowbarMission2Blip = AddBlipForCoord( 2684.30, 3292.18, 54.75 ) SetBlipSprite(crowbarMission2Blip, 225) SetBlipScale(crowbarMission2Blip, 0.85) SetBlipRoute(crowbarMission2Blip, true) BeginTextCommandSetBlipName('STRING') AddTextComponentString("Crowbar's Target") EndTextCommandSetBlipName(crowbarMission2Blip) lib.notify({ title = 'Crowbar', description = 'Find the car Crowbar told you about.', type = 'inform', duration = 5000 }) end ) local crowbarMission2Stolen = false CreateThread(function() while true do Wait(500) if crowbarMission2Vehicle and not crowbarMission2Stolen then local vehicle = NetToVeh(crowbarMission2Vehicle) if DoesEntityExist(vehicle) then local playerPed = PlayerPedId() local vehicleCoords = GetEntityCoords(vehicle) local targetCoords = vector3( 2684.30, 3292.18, 54.75 ) -- The player must actually be driving the stolen -- vehicle away from the target before the mission -- can complete. if GetPedInVehicleSeat( vehicle, -1 ) == playerPed and #(vehicleCoords - targetCoords) >= 100.0 then crowbarMission2Stolen = true if DoesBlipExist(crowbarMission2Blip) then RemoveBlip(crowbarMission2Blip) crowbarMission2Blip = nil end local props = lib.getVehicleProperties(vehicle) TriggerServerEvent( 'how_progression:server:completeCrowbarMission2', crowbarMission2Vehicle, props ) crowbarMission2Vehicle = nil end end end end end) -- ============================================================ -- Crowbar Mission 3: Quick Score -- ============================================================ local crowbarMission3Blip = nil local crowbarMission3EscapeActive = false local crowbarMission3WantedSeen = false RegisterNetEvent( 'how_progression:client:startCrowbarMission3', function() print('[how_progression] Mission 3 client start event received') crowbarMission3EscapeActive = false crowbarMission3WantedSeen = false if DoesBlipExist(crowbarMission3Blip) then RemoveBlip(crowbarMission3Blip) crowbarMission3Blip = nil end crowbarMission3Blip = AddBlipForCoord( 549.33, 2669.57, 42.16 ) SetBlipSprite(crowbarMission3Blip, 52) SetBlipScale(crowbarMission3Blip, 0.85) SetBlipRoute(crowbarMission3Blip, true) print(('[how_progression] Mission 3 Harmony blip created: %s'):format( tostring(crowbarMission3Blip) )) BeginTextCommandSetBlipName('STRING') AddTextComponentString('Harmony 24/7') EndTextCommandSetBlipName(crowbarMission3Blip) end ) RegisterNetEvent( 'how_progression:client:crowbarMission3Escape', function() if DoesBlipExist(crowbarMission3Blip) then RemoveBlip(crowbarMission3Blip) crowbarMission3Blip = nil end crowbarMission3EscapeActive = true -- The robbery may already have raised the wanted level -- before this event reaches the client. if GetPlayerWantedLevel(PlayerId()) > 0 then crowbarMission3WantedSeen = true end lib.notify({ title = 'Crowbar', description = 'Get out of there. Lose the cops.', type = 'inform', duration = 5000 }) end ) CreateThread(function() while true do Wait(500) if crowbarMission3EscapeActive then local wantedLevel = GetPlayerWantedLevel(PlayerId()) if wantedLevel > 0 then crowbarMission3WantedSeen = true elseif crowbarMission3WantedSeen then crowbarMission3EscapeActive = false crowbarMission3WantedSeen = false TriggerServerEvent( 'how_progression:server:completeCrowbarMission3' ) end end end end) -- ============================================================ -- Rewards -- ============================================================ RegisterNetEvent( 'how_progression:client:crowbarReward', function(repReward, totalRep, newStage) lib.notify({ title = 'Crowbar', description = ('+%s Crowbar REP | Total: %s/100'):format( repReward, totalRep ), type = 'success', duration = 5000 }) end ) RegisterNetEvent( 'how_progression:client:crowbarMission1Reward', function(cashReward, repReward) lib.alertDialog({ header = 'Crowbar', content = ( "Not bad.\n\n" .. "You followed directions, brought the car back intact, " .. "and didn't make a complete mess of things.\n\n" .. "Here's your cut: $%s.\n\n" .. "Maybe there's something else you can do for me." ):format(cashReward), centered = true }) end ) -- ============================================================ -- Mack scrapyard ambience -- ============================================================ local scrapyardFestivalBus local scrapyardJunkCar local scrapyardForklift local scrapyardWorkers = {} local function protectScrapyardVehicle(vehicle) SetEntityAsMissionEntity(vehicle, true, true) SetVehicleOnGroundProperly(vehicle) SetVehicleDoorsLocked(vehicle, 2) SetEntityInvincible(vehicle, true) FreezeEntityPosition(vehicle, true) end local function createScrapyardVehicle(modelName, coords) local model = joaat(modelName) lib.requestModel(model) local vehicle = CreateVehicle( model, coords.x, coords.y, coords.z, coords.w, false, false ) protectScrapyardVehicle(vehicle) SetModelAsNoLongerNeeded(model) return vehicle end local function createScrapyardWorker(modelName, coords, radius) local model = joaat(modelName) lib.requestModel(model) local ped = CreatePed( 0, model, coords.x, coords.y, coords.z - 1.0, coords.w, false, false ) SetPedDefaultComponentVariation(ped) SetEntityAsMissionEntity(ped, true, true) SetEntityInvincible(ped, true) SetBlockingOfNonTemporaryEvents(ped, true) SetPedCanRagdoll(ped, false) SetPedFleeAttributes(ped, 0, false) -- Wander around this part of the scrapyard. TaskWanderInArea( ped, coords.x, coords.y, coords.z, radius, 2.0, 10.0 ) SetModelAsNoLongerNeeded(model) scrapyardWorkers[#scrapyardWorkers + 1] = ped return ped end local function createScrapyardAmbience() if not DoesEntityExist(scrapyardFestivalBus) then scrapyardFestivalBus = createScrapyardVehicle( 'pbus2', vec4(2349.54, 3133.84, 48.21, 77.73) ) end if not DoesEntityExist(scrapyardJunkCar) then scrapyardJunkCar = createScrapyardVehicle( 'emperor', vec4(2343.67, 3141.72, 48.21, 211.39) ) -- Make it look like something Mack is stripping for parts. SetVehicleEngineHealth(scrapyardJunkCar, 100.0) SetVehicleBodyHealth(scrapyardJunkCar, 300.0) SmashVehicleWindow(scrapyardJunkCar, 0) SmashVehicleWindow(scrapyardJunkCar, 1) SmashVehicleWindow(scrapyardJunkCar, 2) SmashVehicleWindow(scrapyardJunkCar, 3) SetVehicleDoorBroken(scrapyardJunkCar, 0, true) SetVehicleTyreBurst(scrapyardJunkCar, 0, true, 1000.0) SetVehicleTyreBurst(scrapyardJunkCar, 4, true, 1000.0) end if not DoesEntityExist(scrapyardForklift) then scrapyardForklift = createScrapyardVehicle( 'forklift', vec4(2350.42, 3142.49, 48.21, 231.27) ) end if #scrapyardWorkers == 0 then -- Main yard createScrapyardWorker( 's_m_y_xmech_02', vec4(2355.0, 3148.0, 48.21, 180.0), 12.0 ) createScrapyardWorker( 's_m_m_autoshop_01', vec4(2345.0, 3150.0, 48.21, 90.0), 12.0 ) createScrapyardWorker( 's_m_y_construct_01', vec4(2360.0, 3143.0, 48.21, 270.0), 15.0 ) -- Around warehouse/work areas createScrapyardWorker( 's_m_y_construct_02', vec4(2341.0, 3130.0, 48.21, 0.0), 10.0 ) createScrapyardWorker( 's_m_m_dockwork_01', vec4(2334.0, 3125.0, 48.21, 180.0), 15.0 ) -- Wider-ranging yard hand createScrapyardWorker( 's_m_m_gardener_01', vec4(2368.0, 3130.0, 48.21, 90.0), 22.0 ) end end -- ============================================================ -- Cosmic Dave -- ============================================================ local cosmicDavePed local cosmicDaveCoords = vec4( 2447.63, 3775.08, 41.43, 23.30 ) local function createCosmicDave() if DoesEntityExist(cosmicDavePed) then return end local model = `ig_omega` lib.requestModel(model) cosmicDavePed = CreatePed( 0, model, cosmicDaveCoords.x, cosmicDaveCoords.y, cosmicDaveCoords.z - 1.0, cosmicDaveCoords.w, false, false ) SetPedDefaultComponentVariation(cosmicDavePed) SetEntityAsMissionEntity(cosmicDavePed, true, true) SetEntityInvincible(cosmicDavePed, true) FreezeEntityPosition(cosmicDavePed, true) SetBlockingOfNonTemporaryEvents(cosmicDavePed, true) TaskStartScenarioInPlace( cosmicDavePed, 'WORLD_HUMAN_PARTYING', 0, true ) SetModelAsNoLongerNeeded(model) end -- ============================================================ -- Initial state -- ============================================================ CreateThread(function() Wait(3000) -- Mack's scrapyard physically exists even before the player -- discovers him as a contact. createMack() createScrapyardAmbience() createCosmicDave() local stage = lib.callback.await( 'how_progression:server:getCrowbarMission', false ) if stage and stage >= 1 then unlockCrowbar() end print(( '[how_progression] Initial Crowbar stage: %s' ):format(tostring(stage))) end) RegisterNetEvent( 'how_progression:client:resetProgression', function() ClearGpsPlayerWaypoint() vinnyFenceUnlocked = false mackMission1DeliveryMarkerActive = false if DoesBlipExist(crowbarBlip) then RemoveBlip(crowbarBlip) crowbarBlip = nil end if DoesBlipExist(mackBlip) then RemoveBlip(mackBlip) mackBlip = nil end if DoesBlipExist(gabeBlip) then RemoveBlip(gabeBlip) gabeBlip = nil end if DoesBlipExist(crowbarMission1Blip) then RemoveBlip(crowbarMission1Blip) crowbarMission1Blip = nil end if DoesBlipExist(crowbarMission2Blip) then RemoveBlip(crowbarMission2Blip) crowbarMission2Blip = nil end crowbarMission1Vehicle = nil crowbarMission1ReturnActive = false crowbarMission2Vehicle = nil end ) print('[how_progression] client.lua loaded') RegisterNetEvent( 'how_progression:client:cleanupCrowbarMission1', function() crowbarMission1Vehicle = nil crowbarMission1ReturnActive = false if DoesBlipExist(crowbarMission1Blip) then RemoveBlip(crowbarMission1Blip) crowbarMission1Blip = nil end ClearGpsPlayerWaypoint() end ) RegisterNetEvent( 'how_progression:client:cleanupCrowbarMission2', function() crowbarMission2Vehicle = nil if DoesBlipExist(crowbarMission2Blip) then RemoveBlip(crowbarMission2Blip) crowbarMission2Blip = nil end ClearGpsPlayerWaypoint() end ) -- Remove mission package when its carrier dies. local function crowbarPackageCarrierDied() TriggerServerEvent( 'how_progression:server:crowbarPackageCarrierDied' ) end AddEventHandler( 'baseevents:onPlayerDied', crowbarPackageCarrierDied ) AddEventHandler( 'baseevents:onPlayerKilled', crowbarPackageCarrierDied ) -- Ammu-Nation Body Armor exports('useBodyArmor', function(data, slot) local ped = PlayerPedId() if GetPedArmour(ped) >= 100 then lib.notify({ title = 'Body Armor', description = 'Your armor is already full.', type = 'error' }) return end exports.ox_inventory:useItem(data, function(used) if not used then return end SetPedArmour(ped, 100) lib.notify({ title = 'Body Armor', description = 'Body armor equipped.', type = 'success' }) end) end) -- Temporary NPWD auto-contact bridge RegisterNetEvent('how_progression:client:addPhoneContact', function(contact) exports.npwd:sendNPWDMessage('CONTACTS', 'npwd:autoAddContact', contact) end)