Files

2838 lines
78 KiB
Lua

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)