forked from Simnation/Main
503 lines
16 KiB
Lua
503 lines
16 KiB
Lua
--[[ require ]]
|
|
|
|
local Utils = require 'modules.utils.client'
|
|
local Target = require 'modules.target.client'
|
|
|
|
--[[ state ]]
|
|
|
|
Hack = {
|
|
camId = nil,
|
|
}
|
|
|
|
local onAtmHack = false
|
|
local currentAtmCameraKey = 1
|
|
|
|
local isAimBusy = false
|
|
local aimOnHackable = false
|
|
|
|
local foundEntityByShapeTest = nil
|
|
|
|
local atmParticles = {}
|
|
local ownedCashPiles = {}
|
|
|
|
local selectedAtmEntity = nil
|
|
|
|
--[[ functions ]]
|
|
|
|
local function isAtmModel(model)
|
|
for _, atmModel in pairs(Config.AtmModels) do
|
|
if model == atmModel then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
local function deleteOwnedCashPile(entity)
|
|
for key, value in pairs(ownedCashPiles) do
|
|
if value == entity then
|
|
if DoesEntityExist(value) then
|
|
SetEntityAsMissionEntity(value, true, true)
|
|
DeleteEntity(value)
|
|
end
|
|
table.remove(ownedCashPiles, key)
|
|
return
|
|
end
|
|
end
|
|
end
|
|
|
|
local function createMoneySprayEffect(entity)
|
|
local coords = GetOffsetFromEntityInWorldCoords(entity, 0.0, 0.0, 1.0)
|
|
local ptFxName = 'scr_xs_celebration'
|
|
|
|
local rotX, rotY, rotZ = table.unpack(GetEntityRotation(entity, 2))
|
|
|
|
local atmParticles = {}
|
|
|
|
for i = 1, 10 do
|
|
lib.requestNamedPtfxAsset(ptFxName)
|
|
UseParticleFxAssetNextCall(ptFxName)
|
|
|
|
local randomRotX = 60.0
|
|
local randomRotY = rotY
|
|
local randomRotZ = rotZ
|
|
|
|
local particle = StartParticleFxLoopedAtCoord(
|
|
'scr_xs_money_rain',
|
|
coords.x, coords.y, coords.z,
|
|
randomRotX, randomRotY, randomRotZ,
|
|
1.0, false, false, false, false
|
|
)
|
|
table.insert(atmParticles, particle)
|
|
RemoveNamedPtfxAsset(ptFxName)
|
|
end
|
|
|
|
local particleKey = #atmParticles + 1
|
|
|
|
atmParticles[particleKey] = atmParticles
|
|
|
|
Citizen.SetTimeout(10000, function()
|
|
for _, particle in ipairs(atmParticles[particleKey]) do
|
|
StopParticleFxLooped(particle, 0)
|
|
end
|
|
atmParticles[particleKey] = nil
|
|
end)
|
|
end
|
|
|
|
local function createMoneyObject(entity)
|
|
local coords = GetOffsetFromEntityInWorldCoords(entity, 0.0, -.5, 1.0)
|
|
local rotation = GetEntityRotation(entity)
|
|
local model = 'bkr_prop_bkr_cashpile_05'
|
|
|
|
local objectCashPile = Utils.CreateObject(model, coords, rotation, true, true, false)
|
|
|
|
table.insert(ownedCashPiles, objectCashPile)
|
|
|
|
lib.waitFor(function()
|
|
return PlaceObjectOnGroundProperly(objectCashPile)
|
|
end, nil, 1000)
|
|
|
|
local zoneKey = string.format('atmrobbery_hack_pickup_money_%d', #ownedCashPiles)
|
|
Target.addBoxZone(zoneKey, {
|
|
coords = GetEntityCoords(objectCashPile),
|
|
size = vector3(1.75, 1.75, 1.75),
|
|
debug = false,
|
|
options = {
|
|
{
|
|
icon = 'fa-solid fa-hand-fist',
|
|
label = locale('collect'),
|
|
distance = 1.5,
|
|
pile = objectCashPile,
|
|
onSelect = function(data)
|
|
local playerPedId = cache.ped
|
|
Target.removeZone(zoneKey)
|
|
lib.playAnim(playerPedId, 'pickup_object', 'pickup_low', nil, nil, 1000)
|
|
|
|
Citizen.Wait(1000)
|
|
ClearPedTasks(playerPedId)
|
|
deleteOwnedCashPile(objectCashPile)
|
|
|
|
lib.callback.await(_e('server:hacking:collectAtmCashPile'), false)
|
|
end
|
|
}
|
|
}
|
|
})
|
|
end
|
|
|
|
local function RotationToDirection(rotation)
|
|
local radZ = math.rad(rotation.z)
|
|
local radX = math.rad(rotation.x)
|
|
local num = math.abs(math.cos(radX))
|
|
|
|
return vector3(
|
|
-math.sin(radZ) * num,
|
|
math.cos(radZ) * num,
|
|
math.sin(radX)
|
|
)
|
|
end
|
|
|
|
local function IsPointInFrontOfCamera(camPos, forwardVec, point)
|
|
local pointDirection = (point - camPos)
|
|
local dotProduct = forwardVec.x * pointDirection.x + forwardVec.y * pointDirection.y +
|
|
forwardVec.z * pointDirection.z
|
|
|
|
return dotProduct > 0
|
|
end
|
|
|
|
local function FindHackableObjectFromCamera()
|
|
local hit, entityHit = lib.raycast.fromCamera(16, 4, 15.0)
|
|
|
|
if not hit or not DoesEntityExist(entityHit) then
|
|
return nil
|
|
end
|
|
|
|
local entityModel = GetEntityModel(entityHit)
|
|
local entityPos = GetEntityCoords(entityHit)
|
|
|
|
if not isAtmModel(entityModel) then
|
|
return nil
|
|
end
|
|
|
|
return entityHit
|
|
end
|
|
|
|
local function DisableDisplayControlActions()
|
|
DisableControlAction(0, 263, true) -- disable melee
|
|
DisableControlAction(0, 264, true) -- disable melee
|
|
DisableControlAction(0, 257, true) -- disable melee
|
|
DisableControlAction(0, 140, true) -- disable melee
|
|
DisableControlAction(0, 141, true) -- disable melee
|
|
DisableControlAction(0, 142, true) -- disable melee
|
|
DisableControlAction(0, 143, true) -- disable melee
|
|
DisableControlAction(0, 177, true) -- disable escape
|
|
DisableControlAction(0, 200, true) -- disable escape
|
|
DisableControlAction(0, 202, true) -- disable escape
|
|
DisableControlAction(0, 322, true) -- disable escape
|
|
DisableControlAction(0, 245, true) -- disable chat
|
|
if isAimBusy then
|
|
DisableAllControlActions(0)
|
|
end
|
|
end
|
|
|
|
function deleteHackCam()
|
|
if DoesCamExist(Hack.camId) then
|
|
DestroyCam(Hack.camId)
|
|
ClearFocus()
|
|
RenderScriptCams(false, false, 0, false, false)
|
|
ClearTimecycleModifier()
|
|
ClearExtraTimecycleModifier()
|
|
end
|
|
client.setBusy(false, 'hack.clear')
|
|
Hack.camId = nil
|
|
end
|
|
|
|
function CleanUp()
|
|
deleteHackCam()
|
|
client.SendReactMessage('ui:setVisible', false)
|
|
|
|
if aimOnHackable then
|
|
client.SendReactMessage('ui:setAimOnHackable', false)
|
|
SetEntityDrawOutline(aimOnHackable, false)
|
|
end
|
|
|
|
Utils.ToggleHud(true)
|
|
client.removeTabletFromPlayer()
|
|
aimOnHackable = false
|
|
onAtmHack = false
|
|
selectedAtmEntity = nil
|
|
end
|
|
|
|
function CreateAtmCamera(cameraData)
|
|
local cam = CreateCamWithParams('DEFAULT_SCRIPTED_CAMERA',
|
|
cameraData.coords.x, cameraData.coords.y, cameraData.coords.z,
|
|
cameraData.rot.x, cameraData.rot.y, cameraData.rot.z,
|
|
70.0)
|
|
return cam
|
|
end
|
|
|
|
function ChangeCamera(cam, id)
|
|
ClearFocus()
|
|
local camCoords = Config.AtmCameras[id].coords
|
|
local camRot = Config.AtmCameras[id].rot
|
|
SetCamCoord(cam, camCoords.x, camCoords.y, camCoords.z)
|
|
SetCamRot(cam, camRot.x, camRot.y, camRot.z, 2)
|
|
SetFocusPosAndVel(camCoords.x, camCoords.y, camCoords.z, 0.0, 0.0, 0.0)
|
|
end
|
|
|
|
function CheckCamRotationInput(cam)
|
|
local rightAxisX = GetDisabledControlNormal(0, 220)
|
|
local rightAxisY = GetDisabledControlNormal(0, 221)
|
|
|
|
if rightAxisX ~= 0.0 or rightAxisY ~= 0.0 then
|
|
local rotation = GetCamRot(cam, 2)
|
|
local new_z = rotation.z + rightAxisX * -1.0 * 2.0 * (4.0 + 0.1)
|
|
local new_x = math.max(-60.0, math.min(60.0, rotation.x + rightAxisY * -1.0 * 2.0 * (4.0 + 0.1)))
|
|
SetCamRot(cam, new_x, 0.0, new_z, 2)
|
|
end
|
|
end
|
|
|
|
function HandleControls(cam)
|
|
DisableAllControlActions(0)
|
|
HideHudAndRadarThisFrame()
|
|
|
|
CheckCamRotationInput(cam)
|
|
|
|
if IsDisabledControlJustPressed(0, 34) then
|
|
currentAtmCameraKey = (currentAtmCameraKey - 2) % #Config.AtmCameras + 1
|
|
ChangeCamera(cam, currentAtmCameraKey)
|
|
elseif IsDisabledControlJustPressed(0, 35) then
|
|
currentAtmCameraKey = currentAtmCameraKey % #Config.AtmCameras + 1
|
|
ChangeCamera(cam, currentAtmCameraKey)
|
|
end
|
|
|
|
if IsDisabledControlPressed(0, 97) and GetCamFov(cam) < 100 then
|
|
SetCamFov(cam, GetCamFov(cam) + 2.0)
|
|
elseif IsDisabledControlPressed(0, 96) and GetCamFov(cam) > 10 then
|
|
SetCamFov(cam, GetCamFov(cam) - 2.0)
|
|
end
|
|
|
|
if IsDisabledControlJustPressed(0, 194) then
|
|
CleanUp()
|
|
end
|
|
end
|
|
|
|
function UpdateHackableEntityData()
|
|
local entity = FindHackableObjectFromCamera()
|
|
foundEntityByShapeTest = entity
|
|
end
|
|
|
|
function GetLocationLabel(entity)
|
|
local entityCoords = GetEntityCoords(entity)
|
|
local streetHash, crossingRoadHash = GetStreetNameAtCoord(entityCoords.x, entityCoords.y, entityCoords.z)
|
|
local st1 = GetStreetNameFromHashKey(streetHash)
|
|
local st2 = GetStreetNameFromHashKey(crossingRoadHash)
|
|
return st1 .. ' ' .. st2
|
|
end
|
|
|
|
function HandleHackableObject()
|
|
if foundEntityByShapeTest then
|
|
if not aimOnHackable then
|
|
aimOnHackable = foundEntityByShapeTest
|
|
local targetCoords = GetOffsetFromEntityInWorldCoords(aimOnHackable, 0.2, -0.2, 1.5)
|
|
local _, screenX, screenY = GetScreenCoordFromWorldCoord(targetCoords.x, targetCoords.y, targetCoords.z)
|
|
client.SendReactMessage('ui:setAimOnHackable', true)
|
|
client.SendReactMessage('ui:setAtmInfo', {
|
|
entity = aimOnHackable,
|
|
screenX = screenX,
|
|
screenY = screenY,
|
|
minMoney = Entity(aimOnHackable).state.robbed and 0 or Config.Hack.reward.min,
|
|
maxMoney = Entity(aimOnHackable).state.robbed and 0 or Config.Hack.reward.max,
|
|
location = GetLocationLabel(aimOnHackable)
|
|
})
|
|
SetEntityDrawOutlineColor(255, 0, 0)
|
|
SetEntityDrawOutline(foundEntityByShapeTest, true)
|
|
elseif aimOnHackable ~= foundEntityByShapeTest then
|
|
SetEntityDrawOutline(aimOnHackable, false)
|
|
aimOnHackable = false
|
|
end
|
|
|
|
if not isAimBusy and IsDisabledControlJustPressed(0, 229) then
|
|
local model = GetEntityModel(aimOnHackable)
|
|
local coords = GetEntityCoords(aimOnHackable)
|
|
if lib.callback.await(_e('server:IsAtmHacked'), false, model, coords) then
|
|
Utils.Notify(locale('atm_can_not_be_hack'), 'error')
|
|
Citizen.Wait(500)
|
|
else
|
|
Utils.TriggerPoliceAlert(coords)
|
|
isAimBusy = true
|
|
selectedAtmEntity = aimOnHackable
|
|
client.SendReactMessage('ui:setMiniGameOpen', true)
|
|
SetNuiFocus(true, false)
|
|
end
|
|
end
|
|
elseif aimOnHackable then
|
|
client.SendReactMessage('ui:setAimOnHackable', false)
|
|
SetEntityDrawOutline(aimOnHackable, false)
|
|
aimOnHackable = false
|
|
end
|
|
end
|
|
|
|
function Hack.Clear()
|
|
deleteHackCam()
|
|
|
|
for _, v in ipairs(atmParticles) do
|
|
for _, particle in pairs(v) do
|
|
StopParticleFxLooped(particle, 0)
|
|
end
|
|
end
|
|
|
|
for key, value in pairs(ownedCashPiles) do
|
|
if DoesEntityExist(value) then
|
|
SetEntityAsMissionEntity(value, true, true)
|
|
DeleteEntity(value)
|
|
end
|
|
local zoneKey = string.format('atmrobbery_explode_pickup_money_%d', key)
|
|
Target.removeZone(zoneKey)
|
|
end
|
|
|
|
if onAtmHack then
|
|
CleanUp()
|
|
end
|
|
|
|
atmParticles = {}
|
|
ownedCashPiles = {}
|
|
end
|
|
|
|
function Hack.OpenTablet()
|
|
if onAtmHack then return end
|
|
|
|
onAtmHack = true
|
|
|
|
client.SendReactMessage('ui:setPage', 'tablet')
|
|
client.SendReactMessage('ui:setVisible', true)
|
|
client.setBusy(true, 'hack.OpenTablet')
|
|
|
|
client.giveTabletToPlayer()
|
|
|
|
local cameras = Config.AtmCameras
|
|
|
|
Hack.camId = CreateAtmCamera(cameras[currentAtmCameraKey])
|
|
|
|
SetCamActive(Hack.camId, true)
|
|
RenderScriptCams(true, false, 1, false, true)
|
|
|
|
ChangeCamera(Hack.camId, currentAtmCameraKey)
|
|
SetTimecycleModifier('scanline_cam_cheap')
|
|
SetTimecycleModifierStrength(2.0)
|
|
Utils.ToggleHud(false)
|
|
|
|
Citizen.CreateThread(function()
|
|
while onAtmHack do
|
|
local wait = isAimBusy and 1000 or 1
|
|
HandleControls(Hack.camId)
|
|
HandleHackableObject()
|
|
Citizen.Wait(wait)
|
|
end
|
|
CleanUp()
|
|
end)
|
|
|
|
Citizen.CreateThread(function()
|
|
while onAtmHack do
|
|
local wait = isAimBusy and 1000 or 500
|
|
UpdateHackableEntityData()
|
|
Citizen.Wait(wait)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function Hack.DeletePhone()
|
|
if onAtmHack then CleanUp() end
|
|
end
|
|
|
|
function Hack.OpenHackPhone()
|
|
if onAtmHack then return end
|
|
onAtmHack = true
|
|
|
|
client.SendReactMessage('ui:setPage', 'phone')
|
|
client.SendReactMessage('ui:setVisible', true)
|
|
client.setBusy(true, 'hack.OpenHackPhone')
|
|
client.givePhoneToPlayer()
|
|
Utils.ToggleHud(false)
|
|
|
|
Citizen.CreateThread(function()
|
|
while onAtmHack do
|
|
local wait = 0
|
|
HandleHackableObject()
|
|
|
|
DisableDisplayControlActions()
|
|
if IsDisabledControlJustPressed(0, 194) then
|
|
CleanUp()
|
|
end
|
|
|
|
if aimOnHackable then
|
|
if DoesEntityExist(client.tabletInHand) then
|
|
local phoneCoords = GetEntityCoords(client.tabletInHand)
|
|
local camCoords = GetGameplayCamCoord()
|
|
local camRot = GetGameplayCamRot(2)
|
|
|
|
local camDirection = RotationToDirection(camRot)
|
|
|
|
local distance = 1.0
|
|
local targetCoords = vector3(
|
|
camCoords.x + camDirection.x * distance,
|
|
camCoords.y + camDirection.y * distance,
|
|
camCoords.z + camDirection.z * distance
|
|
)
|
|
|
|
DrawLine(
|
|
phoneCoords.x, phoneCoords.y, phoneCoords.z,
|
|
targetCoords.x, targetCoords.y, targetCoords.z,
|
|
255, 255, 255, 125
|
|
)
|
|
end
|
|
end
|
|
|
|
Citizen.Wait(wait)
|
|
end
|
|
CleanUp()
|
|
end)
|
|
|
|
Citizen.CreateThread(function()
|
|
local holdPhoneAnim = {
|
|
dict = 'cellphone@',
|
|
name = 'cellphone_text_in'
|
|
}
|
|
|
|
while onAtmHack do
|
|
local wait = 500
|
|
UpdateHackableEntityData()
|
|
|
|
local ped = cache.ped
|
|
if not IsEntityPlayingAnim(ped, holdPhoneAnim.dict, holdPhoneAnim.name, 3) then
|
|
lib.playAnim(playerPedId, holdPhoneAnim.dict, holdPhoneAnim.name, 3.0, 3.0, -1, 50)
|
|
end
|
|
|
|
Citizen.Wait(wait)
|
|
end
|
|
end)
|
|
end
|
|
|
|
--[[ events ]]
|
|
|
|
RegisterNUICallback('nui:hacking:hackCurrentAtm', function(_, resultCallback)
|
|
if not selectedAtmEntity or
|
|
not DoesEntityExist(selectedAtmEntity)
|
|
then
|
|
return Utils.Notify(locale('atm_can_not_be_hack'), 'error')
|
|
end
|
|
local model = GetEntityModel(selectedAtmEntity)
|
|
local coords = GetEntityCoords(selectedAtmEntity)
|
|
|
|
TriggerServerEvent(_e('server:hacking:onAtmHackCompleted'), model, coords)
|
|
|
|
Utils.Notify(locale('atm_hacked'), 'success')
|
|
|
|
Entity(selectedAtmEntity).state.robbed = true
|
|
|
|
resultCallback(true)
|
|
end)
|
|
|
|
RegisterNetEvent(_e('client:hacking:onAtmTabletUsed'), function()
|
|
Hack.OpenTablet()
|
|
end)
|
|
|
|
RegisterNetEvent(_e('client:hacking:onHackPhoneUsed'), function()
|
|
Hack.OpenHackPhone()
|
|
end)
|
|
|
|
RegisterNetEvent(_e('client:hacking:onAtmHacked'), function(model, coords)
|
|
local atmEntity = GetClosestObjectOfType(coords.x, coords.y, coords.z, 0.3, model, true)
|
|
if not DoesEntityExist(atmEntity) then return end
|
|
createMoneySprayEffect(atmEntity)
|
|
end)
|
|
|
|
RegisterNetEvent(_e('client:hacking:onHackCompleted'), function(model, coords)
|
|
isAimBusy = false
|
|
SetNuiFocus(false, false)
|
|
|
|
Citizen.SetTimeout(7000, function()
|
|
local atmEntity = GetClosestObjectOfType(coords.x, coords.y, coords.z, 0.3, model, true)
|
|
if not DoesEntityExist(atmEntity) then return end
|
|
createMoneyObject(atmEntity)
|
|
end)
|
|
end)
|