380 lines
13 KiB
Lua
380 lines
13 KiB
Lua
local MODEL_REQUEST_TIMEOUT = 3000
|
|
local CONFIRM_CONTROL = 201
|
|
local CANCEL_CONTROL = 202
|
|
|
|
local function isNearVehicleBones(vehicle, boneNames)
|
|
local playerPed = cache.ped
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
for _, boneName in ipairs(boneNames) do
|
|
local boneIndex = GetEntityBoneIndexByName(vehicle, boneName)
|
|
if boneIndex == -1 then
|
|
return true
|
|
else
|
|
local boneCoords = GetWorldPositionOfEntityBone(vehicle, boneIndex)
|
|
if #(playerCoords - boneCoords) <= 3.0 then
|
|
return true
|
|
end
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function playContextualAnimation(ped, context, vehicle)
|
|
if not ped or not vehicle then
|
|
return
|
|
end
|
|
local vehicleCoords = GetEntityCoords(vehicle)
|
|
local pedCoords = GetEntityCoords(ped)
|
|
if (vehicleCoords.z - pedCoords.z) > 1.0 then
|
|
playAnimation(ped, "missheist_agency2aig_3", "chat_a_worker2")
|
|
elseif context == "engine" then
|
|
playAnimation(ped, "mini@repair", "fixing_a_ped")
|
|
elseif context == "kneeling" then
|
|
playAnimation(ped, "anim@amb@clubhouse@tutorial@bkr_tut_ig3@", "machinic_loop_mechandplayer", true)
|
|
end
|
|
end
|
|
|
|
local function createEngineHoist(coords)
|
|
local engineModel = joaat("prop_car_engine_01")
|
|
local hoistModel = joaat("prop_engine_hoist")
|
|
|
|
lib.requestModel(engineModel, MODEL_REQUEST_TIMEOUT)
|
|
lib.requestModel(hoistModel, MODEL_REQUEST_TIMEOUT)
|
|
|
|
local hoist = CreateObject(hoistModel, coords.x, coords.y, coords.z, true, true, false)
|
|
NetworkSetObjectForceStaticBlend(hoist, true)
|
|
while not HasCollisionLoadedAroundEntity(hoist) do
|
|
Wait(1)
|
|
end
|
|
PlaceObjectOnGroundProperly(hoist)
|
|
|
|
local engine = CreateObject(engineModel, coords.x, coords.y, coords.z, true, true, false)
|
|
NetworkSetObjectForceStaticBlend(engine, true)
|
|
while not HasCollisionLoadedAroundEntity(engine) do
|
|
Wait(1)
|
|
end
|
|
|
|
AttachEntityToEntity(engine, hoist, 0, 0.0, -1.1, 1.25, 0.0, 0.0, 0.0, false, false, true, false, 2, true)
|
|
SetEntityCollision(hoist, false, false)
|
|
SetEntityCanBeDamaged(hoist, false)
|
|
SetEntityCollision(engine, false, true)
|
|
return hoist, engine
|
|
end
|
|
|
|
local function deleteEngineHoist(hoist, engine)
|
|
if hoist then
|
|
DeleteEntity(hoist)
|
|
end
|
|
if engine then
|
|
DeleteEntity(engine)
|
|
end
|
|
end
|
|
|
|
function swapEngineMinigame(vehicle, options, callback)
|
|
local playerPed = cache.ped
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
local offset = vector3(1.5, 0.0, 0.0)
|
|
|
|
TaskLeaveVehicle(playerPed, vehicle, 16)
|
|
Entity(vehicle).state:set("vehicleBonnetDeleted", true, true)
|
|
|
|
local hoist, engine = createEngineHoist(playerCoords)
|
|
|
|
PlaySoundFrontend(-1, "CONTINUE", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
|
|
SetNuiFocus(false, false)
|
|
hideTabletToShowInteractionPrompt(Locale.takeEngineHoistToVehicle)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) then
|
|
break
|
|
end
|
|
if IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
break
|
|
end
|
|
|
|
local currentCoords = GetEntityCoords(playerPed)
|
|
local forwardVector = GetEntityForwardVector(playerPed)
|
|
local rightVector = vector3(forwardVector.y, -forwardVector.x, forwardVector.z)
|
|
local targetCoords = currentCoords + (forwardVector * offset.x) + (rightVector * offset.y) + vector3(0.0, 0.0, offset.z)
|
|
|
|
local foundGround, groundZ = GetGroundZFor_3dCoord(targetCoords.x, targetCoords.y, targetCoords.z, true)
|
|
SetEntityCoords(hoist, targetCoords.x, targetCoords.y, groundZ, true, true, true, false)
|
|
|
|
local heading = math.deg(math.atan((targetCoords.y - currentCoords.y) / (targetCoords.x - currentCoords.x))) - 270.0
|
|
if targetCoords.x < currentCoords.x then
|
|
heading = heading - 180.0
|
|
end
|
|
SetEntityRotation(hoist, 0.0, 0.0, heading, 2, true)
|
|
Wait(0)
|
|
end
|
|
|
|
if IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
showTabletAfterInteractionPrompt()
|
|
stopAnimation(playerPed)
|
|
deleteEngineHoist(hoist, engine)
|
|
Entity(vehicle).state:set("vehicleBonnetDeleted", false, true)
|
|
return callback(false)
|
|
end
|
|
|
|
hideTabletToShowInteractionPrompt(Locale.goToEngineToInstall)
|
|
Wait(100)
|
|
PlaySoundFrontend(-1, "CONTINUE", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
|
|
|
|
while true do
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) and isNearVehicleBones(vehicle, { "engine" }) then
|
|
break
|
|
end
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) then
|
|
Framework.Client.Notify(Locale.notNearbyToEngine, "error")
|
|
Wait(100)
|
|
end
|
|
Wait(0)
|
|
end
|
|
|
|
playContextualAnimation(playerPed, "engine", vehicle)
|
|
local soundId = Framework.Client.PlaySound("repair", playerCoords)
|
|
|
|
Framework.Client.SkillCheck(function()
|
|
showTabletAfterInteractionPrompt()
|
|
stopAnimation(playerPed)
|
|
Framework.Client.StopSound(soundId)
|
|
deleteEngineHoist(hoist, engine)
|
|
Entity(vehicle).state:set("vehicleBonnetDeleted", false, true)
|
|
callback(true)
|
|
end, function()
|
|
showTabletAfterInteractionPrompt()
|
|
stopAnimation(playerPed)
|
|
Framework.Client.StopSound(soundId)
|
|
deleteEngineHoist(hoist, engine)
|
|
Entity(vehicle).state:set("vehicleBonnetDeleted", false, true)
|
|
Framework.Client.Notify(Locale.installationFailed, "error")
|
|
callback(false)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
AddStateBagChangeHandler("vehicleBonnetDeleted", "", function(bagName, key, value)
|
|
local vehicle = GetEntityFromStateBagName(bagName)
|
|
if not vehicle or not DoesEntityExist(vehicle) then
|
|
return
|
|
end
|
|
|
|
if value then
|
|
SetVehicleDoorBroken(vehicle, 4, true)
|
|
else
|
|
SetVehicleFixed(vehicle)
|
|
end
|
|
end)
|
|
|
|
local function createProp(propName, coords)
|
|
local propHash = GetHashKey(propName)
|
|
lib.requestModel(propHash, MODEL_REQUEST_TIMEOUT)
|
|
local prop = CreateObject(propHash, coords.x, coords.y, coords.z, true, true, false)
|
|
NetworkSetObjectForceStaticBlend(prop, true)
|
|
while not HasCollisionLoadedAroundEntity(prop) do
|
|
Wait(1)
|
|
end
|
|
return prop
|
|
end
|
|
|
|
function propBasedMinigame(vehicle, options, callback)
|
|
local propName = options.prop
|
|
if not propName then
|
|
return callback(false)
|
|
end
|
|
|
|
local playerPed = cache.ped
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
TaskLeaveVehicle(playerPed, vehicle, 16)
|
|
|
|
local prop = 0
|
|
local targetBones = {}
|
|
local errorMsg = "ERROR"
|
|
local animContext = "kneeling"
|
|
|
|
if propName == "wheel" then
|
|
targetBones = { "wheel_lf", "wheel_rf", "wheel_lr", "wheel_rr" }
|
|
errorMsg = Locale.notNearWheel
|
|
prop = createProp("prop_wheel_01", playerCoords)
|
|
AttachEntityToEntity(prop, playerPed, 62, 0.0754, 0.0936, 0.2814, -172.34, 0, 0, true, true, false, true, 1, true)
|
|
playAnimation(playerPed, "anim@heists@box_carry@", "idle")
|
|
hideTabletToShowInteractionPrompt(Locale.takeWheel)
|
|
elseif propName == "canister" then
|
|
targetBones = { "engine" }
|
|
errorMsg = Locale.notNearbyToEngine
|
|
animContext = "engine"
|
|
prop = createProp("prop_gascyl_01a", playerCoords)
|
|
AttachEntityToEntity(prop, playerPed, 62, 0.0394, 0.1178, 0.1243, -157.12, 0, 37.73, true, true, false, true, 1, true)
|
|
playAnimation(playerPed, "anim@heists@box_carry@", "idle")
|
|
SetVehicleDoorOpen(vehicle, 4, false, false)
|
|
hideTabletToShowInteractionPrompt(Locale.takeCanisterToEngine)
|
|
elseif propName == "spanner" then
|
|
targetBones = { "engine" }
|
|
errorMsg = Locale.notNearbyToEngine
|
|
animContext = "engine"
|
|
prop = createProp("prop_tool_spanner01", playerCoords)
|
|
AttachEntityToEntity(prop, playerPed, 62, 0.0841, -0.0059, 0.0119, -93.95, -79.54, 21.94, true, true, false, true, 1, true)
|
|
SetVehicleDoorOpen(vehicle, 4, false, false)
|
|
hideTabletToShowInteractionPrompt(Locale.goToEngineToInstallOrCancel)
|
|
end
|
|
|
|
if prop == 0 then
|
|
showTabletAfterInteractionPrompt()
|
|
return callback(false)
|
|
end
|
|
|
|
CreateThread(function()
|
|
SetNuiFocus(false, false)
|
|
Wait(200)
|
|
PlaySoundFrontend(-1, "CONTINUE", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
|
|
|
|
while true do
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) or IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
break
|
|
end
|
|
Wait(0)
|
|
end
|
|
|
|
while true do
|
|
if IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
break
|
|
end
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) and isNearVehicleBones(vehicle, targetBones) then
|
|
break
|
|
end
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) then
|
|
Framework.Client.Notify(errorMsg, "error")
|
|
Wait(100)
|
|
end
|
|
Wait(0)
|
|
end
|
|
|
|
DeleteEntity(prop)
|
|
stopAnimation(playerPed)
|
|
|
|
if IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
SetVehicleDoorsShut(vehicle, false)
|
|
showTabletAfterInteractionPrompt()
|
|
return callback(false)
|
|
end
|
|
|
|
Wait(200)
|
|
playContextualAnimation(playerPed, animContext, vehicle)
|
|
local soundId = Framework.Client.PlaySound("repair", playerCoords)
|
|
|
|
Framework.Client.SkillCheck(function()
|
|
showTabletAfterInteractionPrompt()
|
|
stopAnimation(playerPed)
|
|
DeleteEntity(prop)
|
|
SetVehicleDoorsShut(vehicle, false)
|
|
Framework.Client.StopSound(soundId)
|
|
callback(true)
|
|
end, function()
|
|
showTabletAfterInteractionPrompt()
|
|
stopAnimation(playerPed)
|
|
DeleteEntity(prop)
|
|
SetVehicleDoorsShut(vehicle, false)
|
|
Framework.Client.StopSound(soundId)
|
|
Framework.Client.Notify(Locale.installationFailed, "error")
|
|
callback(false)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
function paintSprayMinigame(vehicle, options, callback)
|
|
local playerPed = cache.ped
|
|
local playerCoords = GetEntityCoords(playerPed)
|
|
TaskLeaveVehicle(playerPed, vehicle, 16)
|
|
|
|
CreateThread(function()
|
|
local animDict = "switch@franklin@cleaning_car"
|
|
local animName = "001946_01_gc_fras_v2_ig_5_base"
|
|
local propName = "prop_paint_spray01b"
|
|
local pfxAsset = "core"
|
|
local pfxName = "ent_sht_steam"
|
|
|
|
lib.requestAnimDict(animDict)
|
|
lib.requestModel(propName, MODEL_REQUEST_TIMEOUT)
|
|
lib.requestNamedPtfxAsset(pfxAsset)
|
|
|
|
local sprayCan = CreateObject(GetHashKey(propName), playerCoords.x, playerCoords.y, playerCoords.z, true, true, true)
|
|
AttachEntityToEntity(sprayCan, playerPed, 71, 0.05, 0.0, -0.02, 0.0, 90.0, 90.0, true, true, false, true, 1, true)
|
|
SetNuiFocus(false, false)
|
|
hideTabletToShowInteractionPrompt(Locale.pressToRespray)
|
|
Wait(200)
|
|
PlaySoundFrontend(-1, "CONTINUE", "HUD_FRONTEND_DEFAULT_SOUNDSET", false)
|
|
|
|
while true do
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) or IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
break
|
|
end
|
|
Wait(0)
|
|
end
|
|
|
|
while true do
|
|
if IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
break
|
|
end
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) then
|
|
local dist = #(GetEntityCoords(playerPed) - GetEntityCoords(vehicle))
|
|
if not (dist > 4.0) then
|
|
break
|
|
end
|
|
end
|
|
if IsControlJustPressed(0, CONFIRM_CONTROL) then
|
|
Framework.Client.Notify(Locale.tooFarFromVehicle, "error")
|
|
Wait(100)
|
|
end
|
|
Wait(0)
|
|
end
|
|
|
|
if IsControlJustReleased(0, CANCEL_CONTROL) then
|
|
DeleteObject(sprayCan)
|
|
ClearPedTasksImmediately(playerPed)
|
|
showTabletAfterInteractionPrompt()
|
|
return callback(false)
|
|
end
|
|
|
|
Wait(200)
|
|
hideTabletToShowInteractionPrompt(Locale.paintEvenlyMsg)
|
|
TaskPlayAnim(playerPed, animDict, animName, 8.0, -8, -1, 49, 0, false, false, false)
|
|
|
|
CreateThread(function()
|
|
for i = 0, 2 do
|
|
UseParticleFxAssetNextCall(pfxAsset)
|
|
local pfx = StartParticleFxLoopedOnEntity(pfxName, sprayCan, 0.0, 0.0, 0.15, 0.0, 0.0, 0.0, 1.0, false, false, false)
|
|
Citizen.Wait(5000)
|
|
StopParticleFxLooped(pfx, false)
|
|
end
|
|
end)
|
|
|
|
Framework.Client.ProgressBar(Locale.resprayingVehicleProgress, 15000, false, false, function()
|
|
showTabletAfterInteractionPrompt()
|
|
DeleteObject(sprayCan)
|
|
ClearPedTasksImmediately(playerPed)
|
|
callback(true)
|
|
end, function()
|
|
showTabletAfterInteractionPrompt()
|
|
DeleteObject(sprayCan)
|
|
ClearPedTasksImmediately(playerPed)
|
|
callback(false)
|
|
end)
|
|
end)
|
|
end
|
|
|
|
function playMinigame(vehicle, minigameType, options, callback)
|
|
if not vehicle or vehicle == 0 then
|
|
return callback(false)
|
|
end
|
|
|
|
local minigameFunc = propBasedMinigame
|
|
if minigameType == "respray" then
|
|
minigameFunc = paintSprayMinigame
|
|
elseif minigameType == "engineSwap" then
|
|
minigameFunc = swapEngineMinigame
|
|
end
|
|
|
|
minigameFunc(vehicle, options, callback)
|
|
end
|