294 lines
13 KiB
Lua
294 lines
13 KiB
Lua
|
|
local config = require 'config.server'
|
||
|
|
local sharedConfig = require 'config.shared'
|
||
|
|
local startedLoot = {}
|
||
|
|
local startedPickup = {}
|
||
|
|
|
||
|
|
-- Returns closes house index number from sharedConfig table
|
||
|
|
---@param coords vector3 Point to check for closest house point
|
||
|
|
---@return integer
|
||
|
|
local function getClosestHouse(coords)
|
||
|
|
local closestHouseIndex
|
||
|
|
for i = 1, #sharedConfig.houses do
|
||
|
|
if #(coords - sharedConfig.houses[i].coords) <= 3 then
|
||
|
|
if closestHouseIndex then
|
||
|
|
if #(coords - sharedConfig.houses[i].coords) < #(coords - sharedConfig.houses[closestHouseIndex].coords) then
|
||
|
|
closestHouseIndex = i
|
||
|
|
end
|
||
|
|
else
|
||
|
|
closestHouseIndex = i
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return closestHouseIndex
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Teleports player to house exit inside IPL interior
|
||
|
|
-- Sets routing bucket for player
|
||
|
|
-- Triggers loot point creation for client
|
||
|
|
---@param source number Player server Id
|
||
|
|
---@param coords vector4 Destination coordinates to teleport player
|
||
|
|
---@param bucket number Routing bucket to put player in
|
||
|
|
---@param closestHouseIndex number House index to store with player citizenid so we know what house they are in
|
||
|
|
local function enterHouse(source, coords, bucket, closestHouseIndex)
|
||
|
|
local player = exports.qbx_core:GetPlayer(source)
|
||
|
|
SetResourceKvpInt(player.PlayerData.citizenid, closestHouseIndex)
|
||
|
|
TriggerClientEvent('qb-interior:client:screenfade', source)
|
||
|
|
Wait(200)
|
||
|
|
local ped = GetPlayerPed(source)
|
||
|
|
SetEntityCoords(ped, coords.x, coords.y, coords.z, false, false, false, false)
|
||
|
|
SetEntityHeading(ped, coords.w)
|
||
|
|
exports.qbx_core:SetPlayerBucket(source, bucket)
|
||
|
|
TriggerClientEvent('qbx_houserobbery:client:enterHouse', source)
|
||
|
|
FreezeEntityPosition(ped, true)
|
||
|
|
Wait(200)
|
||
|
|
FreezeEntityPosition(ped, false)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Returns player to house entrace in routing bucket 0
|
||
|
|
---@param source number
|
||
|
|
---@param coords vector3
|
||
|
|
local function leaveHouse(source, coords)
|
||
|
|
TriggerClientEvent('qb-interior:client:screenfade', source)
|
||
|
|
Wait(200)
|
||
|
|
local ped = GetPlayerPed(source)
|
||
|
|
SetEntityCoords(ped, coords.x, coords.y, coords.z, false, false, false, false)
|
||
|
|
exports.qbx_core:SetPlayerBucket(source, 0)
|
||
|
|
FreezeEntityPosition(ped, true)
|
||
|
|
Wait(200)
|
||
|
|
FreezeEntityPosition(ped, false)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Shuffle loot tables
|
||
|
|
---@param index number House interior table to shuffle loot
|
||
|
|
local function shuffleTables(index)
|
||
|
|
for i = #sharedConfig.interiors[index].loot, 2, -1 do
|
||
|
|
local j = math.random(i)
|
||
|
|
sharedConfig.interiors[index].loot[i], sharedConfig.interiors[index].loot[j] = sharedConfig.interiors[index].loot[j], sharedConfig.interiors[index].loot[i]
|
||
|
|
end
|
||
|
|
for i = #sharedConfig.interiors[index].pickups, 2, -1 do
|
||
|
|
local j = math.random(i)
|
||
|
|
sharedConfig.interiors[index].pickups[i], sharedConfig.interiors[index].pickups[j] = sharedConfig.interiors[index].pickups[j], sharedConfig.interiors[index].pickups[i]
|
||
|
|
end
|
||
|
|
for b = 1, #config.rewards do
|
||
|
|
for i = #config.rewards[b].items, 2, -1 do
|
||
|
|
local j = math.random(i)
|
||
|
|
config.rewards[b].items[i], config.rewards[b].items[j] = config.rewards[b].items[j], config.rewards[b].items[i]
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Alert police to house robbery in progress
|
||
|
|
---@param text string Text to send
|
||
|
|
---@param interiorId number Interior index number to fetch timeout from config
|
||
|
|
local function policeAlert(text, interiorId)
|
||
|
|
SetTimeout(sharedConfig.interiors[interiorId].callCopsTimeout, function()
|
||
|
|
TriggerEvent('police:server:policeAlert', text)
|
||
|
|
end)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Lockpick event handler for entering houses.
|
||
|
|
-- Triggers skillcheck callback on calling player before teleporting them inside
|
||
|
|
---@param playerSource number Player server Id
|
||
|
|
---@param isAdvanced boolean Is this an advanced lockpick
|
||
|
|
AddEventHandler('lockpicks:UseLockpick', function(playerSource, isAdvanced)
|
||
|
|
local player = exports.qbx_core:GetPlayer(playerSource)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(playerSource))
|
||
|
|
local closestHouseIndex = getClosestHouse(playerCoords)
|
||
|
|
local house = sharedConfig.houses[closestHouseIndex]
|
||
|
|
local amount = exports.qbx_core:GetDutyCountType('leo')
|
||
|
|
|
||
|
|
if not house then return end
|
||
|
|
if house.opened then return end
|
||
|
|
if not isAdvanced and not player.Functions.GetItemByName(config.requiredItems[2]) then return end
|
||
|
|
if amount < config.minimumPolice then
|
||
|
|
if config.notEnoughCopsNotify then
|
||
|
|
exports.qbx_core:Notify(playerSource, locale('notify.no_police', config.minimumPolice), 'error')
|
||
|
|
return
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
local result = lib.callback.await('qbx_houserobbery:client:checkTime', playerSource)
|
||
|
|
|
||
|
|
if not result then return end
|
||
|
|
|
||
|
|
local skillcheck = lib.callback.await('qbx_houserobbery:client:startSkillcheck', playerSource, sharedConfig.interiors[house.interior].skillcheck)
|
||
|
|
|
||
|
|
if skillcheck then
|
||
|
|
sharedConfig.houses[closestHouseIndex].opened = true
|
||
|
|
exports.qbx_core:Notify(playerSource, locale('notify.success_skillcheck'), 'success')
|
||
|
|
TriggerClientEvent('qbx_houserobbery:client:syncconfig', -1, sharedConfig.houses[closestHouseIndex], closestHouseIndex)
|
||
|
|
enterHouse(playerSource, sharedConfig.interiors[house.interior].exit, house.routingbucket, closestHouseIndex)
|
||
|
|
policeAlert(locale('notify.police_alert'), house.interior)
|
||
|
|
else
|
||
|
|
exports.qbx_core:Notify(playerSource, locale('notify.fail_skillcheck'), 'error')
|
||
|
|
end
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- Teleports player inside house and sets routing bucket.
|
||
|
|
---@param index number House index number to locate in config
|
||
|
|
RegisterNetEvent('qbx_houserobbery:server:enterHouse', function(index)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source --[[@as number]]))
|
||
|
|
local closestHouseIndex = getClosestHouse(playerCoords)
|
||
|
|
|
||
|
|
if closestHouseIndex ~= index then return end
|
||
|
|
if not closestHouseIndex then return end
|
||
|
|
if not sharedConfig.houses[index].opened then return end
|
||
|
|
|
||
|
|
enterHouse(source --[[@as number]], sharedConfig.interiors[sharedConfig.houses[closestHouseIndex].interior].exit, sharedConfig.houses[closestHouseIndex].routingbucket, closestHouseIndex)
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- NetEvent to handle player exiting house
|
||
|
|
RegisterNetEvent('qbx_houserobbery:server:leaveHouse', function()
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source --[[@as number]]))
|
||
|
|
local index = GetResourceKvpInt(exports.qbx_core:GetPlayer(source).PlayerData.citizenid)
|
||
|
|
local exit = vec3(sharedConfig.interiors[sharedConfig.houses[index].interior].exit.x, sharedConfig.interiors[sharedConfig.houses[index].interior].exit.y, sharedConfig.interiors[sharedConfig.houses[index].interior].exit.z)
|
||
|
|
|
||
|
|
if #(playerCoords - exit) > 3 then return end
|
||
|
|
|
||
|
|
leaveHouse(source --[[@as number]], sharedConfig.houses[index].coords)
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- Callback to check if loot is busy/already looted
|
||
|
|
---@param source number Player server Id
|
||
|
|
---@param houseIndex number House index from sharedConfig
|
||
|
|
---@param lootIndex number Loot index from sharedConfig (dynamically generated)
|
||
|
|
---@return boolean?
|
||
|
|
lib.callback.register('qbx_houserobbery:server:checkLoot', function(source, houseIndex, lootIndex)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source))
|
||
|
|
local loot = sharedConfig.houses[houseIndex].loot[lootIndex]
|
||
|
|
|
||
|
|
if #(playerCoords - loot.coords) > 3 then return end
|
||
|
|
if loot.isBusy then exports.qbx_core:Notify(source, locale('notify.busy')) return end
|
||
|
|
if loot.isOpened then return end
|
||
|
|
if not sharedConfig.houses[houseIndex].opened then return end
|
||
|
|
|
||
|
|
startedLoot[source] = true
|
||
|
|
sharedConfig.houses[houseIndex].loot[lootIndex].isBusy = true
|
||
|
|
return true
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- NetEvent to update status of loot drops inside house and give reward
|
||
|
|
---@param houseIndex number House index from sharedConfig
|
||
|
|
---@param lootIndex number Loot index from sharedConfig (dynamically generated)
|
||
|
|
RegisterNetEvent('qbx_houserobbery:server:lootFinished', function(houseIndex, lootIndex)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source --[[@as number]]))
|
||
|
|
local player = exports.qbx_core:GetPlayer(source)
|
||
|
|
local loot = sharedConfig.houses[houseIndex].loot[lootIndex]
|
||
|
|
local reward = config.rewards[loot.pool[math.random(#loot.pool)]]
|
||
|
|
|
||
|
|
if #(playerCoords - loot.coords) > 3 then return end
|
||
|
|
if not startedLoot[source] then return end
|
||
|
|
if not loot.isBusy then return end
|
||
|
|
if loot.isOpened then return end
|
||
|
|
|
||
|
|
for i = 1, math.random(reward.togive.min, reward.togive.max) do
|
||
|
|
player.Functions.AddItem(reward.items[i], math.random(reward.toget.min, reward.toget.max))
|
||
|
|
end
|
||
|
|
startedLoot[source] = false
|
||
|
|
sharedConfig.houses[houseIndex].loot[lootIndex].isBusy = false
|
||
|
|
sharedConfig.houses[houseIndex].loot[lootIndex].isOpened = true
|
||
|
|
TriggerClientEvent('qbx_houserobbery:client:syncconfig', -1, sharedConfig.houses[houseIndex], houseIndex)
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- NetEvent to handle cancelling loot attempt
|
||
|
|
---@param houseIndex number House index from sharedConfig
|
||
|
|
---@param lootIndex number Loot index from sharedConfig (dynamically generated)
|
||
|
|
RegisterNetEvent('qbx_houserobbery:server:lootCancelled', function(houseIndex, lootIndex)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source --[[@as number]]))
|
||
|
|
|
||
|
|
if #(playerCoords - sharedConfig.houses[houseIndex].loot[lootIndex].coords) > 3 then return end
|
||
|
|
if not startedLoot[source] then return end
|
||
|
|
|
||
|
|
startedLoot[source] = false
|
||
|
|
sharedConfig.houses[houseIndex].loot[lootIndex].isBusy = false
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- Callback to check if pickup point is busy or looted
|
||
|
|
---@param source number Player server Id
|
||
|
|
---@param houseIndex number House index from sharedConfig
|
||
|
|
---@param pickupIndex number Pickup index from sharedConfig (dynamically generated)
|
||
|
|
---@return boolean?
|
||
|
|
lib.callback.register('qbx_houserobbery:server:checkPickup', function(source, houseIndex, pickupIndex)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source))
|
||
|
|
local house = sharedConfig.houses[houseIndex]
|
||
|
|
if not house or not house.pickups then return end
|
||
|
|
|
||
|
|
local pickup = house.pickups[pickupIndex]
|
||
|
|
if not pickup or #(playerCoords - pickup.coords) > 3 then return end
|
||
|
|
if pickup.isBusy then
|
||
|
|
exports.qbx_core:Notify(source, locale('notify.busy'))
|
||
|
|
return
|
||
|
|
end
|
||
|
|
if pickup.isOpened then return end
|
||
|
|
startedPickup[source] = true
|
||
|
|
sharedConfig.houses[houseIndex].pickups[pickupIndex].isBusy = true
|
||
|
|
return true
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- NetEvent to update pickup point status and give reward
|
||
|
|
---@param houseIndex number House index from sharedConfig
|
||
|
|
---@param pickupIndex number Pickup index from sharedConfig (dynamically generated)
|
||
|
|
RegisterNetEvent('qbx_houserobbery:server:pickupFinished', function(houseIndex, pickupIndex)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source --[[@as number]]))
|
||
|
|
local player = exports.qbx_core:GetPlayer(source)
|
||
|
|
local pickup = sharedConfig.houses[houseIndex].pickups[pickupIndex]
|
||
|
|
|
||
|
|
if #(playerCoords - pickup.coords) > 3 then return end
|
||
|
|
if not startedPickup[source] then return end
|
||
|
|
if not pickup.isBusy then return end
|
||
|
|
if pickup.isOpened then return end
|
||
|
|
|
||
|
|
player.Functions.AddItem(pickup.reward, 1)
|
||
|
|
startedPickup[source] = false
|
||
|
|
sharedConfig.houses[houseIndex].pickups[pickupIndex].isBusy = false
|
||
|
|
sharedConfig.houses[houseIndex].pickups[pickupIndex].isOpened = true
|
||
|
|
TriggerClientEvent('qbx_houserobbery:client:syncconfig', -1, sharedConfig.houses[houseIndex], houseIndex)
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- NetEvent to handle cancelling pickup attempt
|
||
|
|
---@param houseIndex number House index from sharedConfig
|
||
|
|
---@param pickupIndex number Pickup index from sharedConfig (dynamically generated)
|
||
|
|
RegisterNetEvent('qbx_houserobbery:server:pickupCancelled', function(houseIndex, pickupIndex)
|
||
|
|
local playerCoords = GetEntityCoords(GetPlayerPed(source --[[@as number]]))
|
||
|
|
|
||
|
|
if #(playerCoords - sharedConfig.houses[houseIndex].pickups[pickupIndex].coords) > 3 then return end
|
||
|
|
if not startedPickup[source] then return end
|
||
|
|
|
||
|
|
startedPickup[source] = false
|
||
|
|
sharedConfig.houses[houseIndex].pickups[pickupIndex].isBusy = false
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- Startup thread to shuffle loot for all houses in configuration and sync configuration to clients
|
||
|
|
CreateThread(function()
|
||
|
|
for i = 1, #sharedConfig.houses do
|
||
|
|
shuffleTables(sharedConfig.houses[i].interior)
|
||
|
|
local randomAmountOfLoot = math.random(sharedConfig.houses[i].setup.loot.min, sharedConfig.houses[i].setup.loot.max)
|
||
|
|
for b = 1, randomAmountOfLoot do
|
||
|
|
sharedConfig.houses[i].loot[b] = {
|
||
|
|
coords = sharedConfig.interiors[sharedConfig.houses[i].interior].loot[b].coords,
|
||
|
|
pool = sharedConfig.interiors[sharedConfig.houses[i].interior].loot[b].pool,
|
||
|
|
isBusy = false,
|
||
|
|
isOpened = false
|
||
|
|
}
|
||
|
|
end
|
||
|
|
local randomAmountOfPickups = math.random(sharedConfig.houses[i].setup.pickups.min, sharedConfig.houses[i].setup.pickups.max)
|
||
|
|
for b = 1, randomAmountOfPickups do
|
||
|
|
sharedConfig.houses[i].pickups[b] = {
|
||
|
|
coords = sharedConfig.interiors[sharedConfig.houses[i].interior].pickups[b].coords,
|
||
|
|
prop = sharedConfig.interiors[sharedConfig.houses[i].interior].pickups[b].model,
|
||
|
|
reward = sharedConfig.interiors[sharedConfig.houses[i].interior].pickups[b].reward,
|
||
|
|
entity = {},
|
||
|
|
isBusy = false,
|
||
|
|
isOpened = false
|
||
|
|
}
|
||
|
|
end
|
||
|
|
end
|
||
|
|
Wait(50)
|
||
|
|
TriggerClientEvent('qbx_houserobbery:client:syncconfig', -1, sharedConfig.houses)
|
||
|
|
end)
|
||
|
|
|
||
|
|
-- Event handler to sync configuration to new players joining server
|
||
|
|
AddEventHandler('playerJoining', function()
|
||
|
|
TriggerClientEvent('qbx_houserobbery:client:syncconfig', source, sharedConfig.houses)
|
||
|
|
end)
|