forked from Simnation/Main
462 lines
18 KiB
Lua
462 lines
18 KiB
Lua
![]() |
if not Config.UseTarget then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
local target_name = GetResourceState('ox_target'):find('started') and 'qtarget' or 'qb-target'
|
||
|
|
||
|
---@class Target
|
||
|
---@field houses table
|
||
|
Target = {
|
||
|
zones = {},
|
||
|
}
|
||
|
|
||
|
local function checkKey()
|
||
|
if CurrentHouse ~= nil and CurrentHouseData.haskey then
|
||
|
return true
|
||
|
end
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
local lastMLODoors = {}
|
||
|
|
||
|
function Target:initMLODoors(key)
|
||
|
local houseData = self.houses[key]
|
||
|
if not houseData then return end
|
||
|
if not houseData.mlo then return end
|
||
|
local hashes = {}
|
||
|
for doorId, data in pairs(houseData.mlo) do
|
||
|
local has = table.find(hashes, function(v)
|
||
|
return v == data.hash
|
||
|
end)
|
||
|
if not has then
|
||
|
table.insert(hashes, data.hash)
|
||
|
end
|
||
|
end
|
||
|
lastMLODoors = hashes
|
||
|
local confirmed = {}
|
||
|
exports[target_name]:AddTargetModel(hashes, {
|
||
|
options = {
|
||
|
{
|
||
|
icon = 'fa-solid fa-door-open',
|
||
|
label = Lang('HOUSING_TARGET_TOGGLE_DOOR'),
|
||
|
action = function(entity)
|
||
|
local coords = GetEntityCoords(entity)
|
||
|
local finded, doorId = table.find(houseData.mlo, function(door)
|
||
|
local doorCoords = vec3(door.coords.x, door.coords.y, door.coords.z)
|
||
|
local distance = #(coords - doorCoords)
|
||
|
return distance < Config.DoorDistance
|
||
|
end)
|
||
|
if not finded then return end
|
||
|
local doorData = houseData.mlo[doorId]
|
||
|
if not checkKey() then return Notification(Lang('HOUSING_NOTIFICATION_NO_KEYS'), 'error') end
|
||
|
RequestAnimDict('anim@heists@keycard@')
|
||
|
while not HasAnimDictLoaded('anim@heists@keycard@') do
|
||
|
Citizen.Wait(1)
|
||
|
end
|
||
|
TaskPlayAnim(PlayerPedId(), 'anim@heists@keycard@', 'exit', 8.0, 8.0, 1000, 1, 1, 0, 0, 0)
|
||
|
TriggerServerEvent('qb-houses:SyncDoor', CurrentHouse, { finded }, not doorData.locked)
|
||
|
end,
|
||
|
canInteract = function(entity)
|
||
|
if not CurrentHouse then
|
||
|
return false
|
||
|
end
|
||
|
if confirmed[entity] then
|
||
|
return true
|
||
|
end
|
||
|
local coords = GetEntityCoords(entity)
|
||
|
local finded = table.find(houseData.mlo, function(door)
|
||
|
local doorCoords = vec3(door.coords.x, door.coords.y, door.coords.z)
|
||
|
local distance = #(coords - doorCoords)
|
||
|
return distance < Config.DoorDistance
|
||
|
end)
|
||
|
if not finded then
|
||
|
return false
|
||
|
end
|
||
|
confirmed[entity] = finded
|
||
|
return true
|
||
|
end
|
||
|
},
|
||
|
},
|
||
|
distance = 2.5
|
||
|
})
|
||
|
end
|
||
|
|
||
|
function Target:initObjectInteractions()
|
||
|
local hashes = {}
|
||
|
for a, x in pairs(Config.DynamicFurnitures) do
|
||
|
table.insert(hashes, GetHashKey(a))
|
||
|
end
|
||
|
|
||
|
exports[target_name]:AddTargetModel(hashes, {
|
||
|
options = {
|
||
|
{
|
||
|
icon = 'fa-solid fa-magnifying-glass',
|
||
|
label = Lang('HOUSING_TARGET_FURNITURE_INTERACTION'),
|
||
|
action = function(entity) -- This is the action it has to perform, this REPLACES the event and this is OPTIONAL
|
||
|
local decorations = ObjectList
|
||
|
if not decorations then return end
|
||
|
local decorationData = table.find(decorations, function(decoration)
|
||
|
return GetHashKey(decoration.modelName) == GetEntityModel(entity) and decoration.handle == entity
|
||
|
end)
|
||
|
local objectData = table.find(Config.DynamicFurnitures, function(furniData, key)
|
||
|
return GetHashKey(key) == GetEntityModel(entity)
|
||
|
end)
|
||
|
if not objectData then return print('No objectData') end
|
||
|
if not decorationData then return print('No decorationData') end
|
||
|
if objectData.event then
|
||
|
local uniq = decorationData.uniq
|
||
|
TriggerEvent(objectData.event, uniq)
|
||
|
return
|
||
|
end
|
||
|
if objectData.type == 'stash' then
|
||
|
local uniq = decorationData.uniq
|
||
|
if CanAccessStash(uniq) then
|
||
|
openStash(objectData.stash, uniq)
|
||
|
end
|
||
|
elseif objectData.type == 'gardrobe' then
|
||
|
openWardrobe()
|
||
|
end
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data) -- This will check if you can interact with it, this won't show up if it returns false, this is OPTIONAL
|
||
|
local house = CurrentHouse
|
||
|
if not house then
|
||
|
return false
|
||
|
end
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
{
|
||
|
icon = 'fa-solid fa-magnifying-glass',
|
||
|
label = Lang('HOUSING_MENU_VAULT_SET_CODE'),
|
||
|
action = function(entity) -- This is the action it has to perform, this REPLACES the event and this is OPTIONAL
|
||
|
local house = CurrentHouse
|
||
|
local decorations = ObjectList
|
||
|
if not decorations then
|
||
|
Notification(Lang('HOUSING_NOTIFICATION_VAULT_CANNOT_DECORATRIONS'), 'error')
|
||
|
return
|
||
|
end
|
||
|
local decorationData = table.find(decorations, function(decoration)
|
||
|
return GetHashKey(decoration.modelName) == GetEntityModel(entity)
|
||
|
end)
|
||
|
local objectData = table.find(Config.DynamicFurnitures, function(furniData, key)
|
||
|
return GetHashKey(key) == GetEntityModel(entity)
|
||
|
end)
|
||
|
if not objectData then
|
||
|
Notification(Lang('HOUSING_NOTIFICATION_VAULT_CANNOT_OBJECT_DATA'), 'error')
|
||
|
return
|
||
|
end
|
||
|
if not decorationData then
|
||
|
Notification(Lang('HOUSING_NOTIFICATION_VAULT_CANOT_DECORATION_DATA'), 'error')
|
||
|
return
|
||
|
end
|
||
|
if objectData.type == 'stash' then
|
||
|
local uniq = decorationData.uniq
|
||
|
OpenVaultCodeMenu(uniq)
|
||
|
return
|
||
|
end
|
||
|
Notification(Lang('HOUSING_NOTIFICATION_VAULT_CANNOT_SET_CODE'), 'error')
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data) -- This will check if you can interact with it, this won't show up if it returns false, this is OPTIONAL
|
||
|
if not CurrentHouseData.isOfficialOwner then return false end
|
||
|
local houseData = Config.Houses[CurrentHouse]
|
||
|
return table.includes(houseData.upgrades, 'vault')
|
||
|
end,
|
||
|
}
|
||
|
},
|
||
|
distance = Config.TargetLength,
|
||
|
})
|
||
|
Target.initObjectInteractions = nil
|
||
|
end
|
||
|
|
||
|
local function checkHouseHasOwner()
|
||
|
if not CurrentHouseData.isOwned or CurrentHouseData.rentable or CurrentHouseData.purchasable then return false end
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
function Target:initOutside(key)
|
||
|
local houseData = self.houses[key]
|
||
|
local enterCoords = vec3(houseData.coords.enter.x, houseData.coords.enter.y, houseData.coords.enter.z)
|
||
|
local options = {}
|
||
|
if houseData.apartmentNumber then
|
||
|
table.insert(options, {
|
||
|
icon = 'fa-solid fa-magnifying-glass',
|
||
|
label = Lang('HOUSING_TARGET_SHOW_APARTMENTS'),
|
||
|
action = function()
|
||
|
OpenApartmentMenu()
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
if checkHouseHasOwner() then return false end
|
||
|
return true
|
||
|
end,
|
||
|
})
|
||
|
elseif not houseData.apartmentNumber then
|
||
|
options = {
|
||
|
{
|
||
|
icon = 'fa-solid fa-magnifying-glass',
|
||
|
label = Lang('HOUSING_TARGET_SHOW_HOUSE'),
|
||
|
action = function()
|
||
|
InspectHouse(houseData)
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
if checkHouseHasOwner() then return false end
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
{
|
||
|
icon = 'fas fa-file-contract',
|
||
|
label = Lang('HOUSING_TARGET_VIEW_HOUSE'),
|
||
|
action = function()
|
||
|
if CurrentHouseData.rentable then
|
||
|
TriggerServerEvent('qb-houses:server:viewHouse', CurrentHouse, true)
|
||
|
else
|
||
|
TriggerServerEvent('qb-houses:server:viewHouse', CurrentHouse)
|
||
|
end
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
if checkHouseHasOwner() then return false end
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
{
|
||
|
icon = 'fa-solid fa-door-open',
|
||
|
label = Lang('HOUSING_TARGET_ENTER_HOUSE'),
|
||
|
action = function()
|
||
|
TriggerEvent('qb-houses:client:EnterHouse', houseData.ipl)
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
if not CurrentHouse then return false end
|
||
|
if Config.Houses[CurrentHouse].mlo then return false end
|
||
|
if not checkHouseHasOwner() then return false end
|
||
|
if not CurrentHouseData.haskey and not Config.Houses[CurrentHouse].IsRammed then return false end
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
{
|
||
|
icon = 'fa-solid fa-bell',
|
||
|
label = Lang('HOUSING_TARGET_REQUEST_RING'),
|
||
|
action = function()
|
||
|
TriggerEvent('qb-houses:client:RequestRing')
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
if not CurrentHouse then return false end
|
||
|
if Config.Houses[CurrentHouse].mlo then return false end
|
||
|
if not checkHouseHasOwner() then return false end
|
||
|
if CurrentHouseData.haskey or Config.Houses[CurrentHouse].IsRammed then return false end
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
}
|
||
|
end
|
||
|
if #options == 0 then return end
|
||
|
exports[target_name]:AddBoxZone('house_outside' .. key, enterCoords, Config.TargetLength, Config.TargetWidth, {
|
||
|
name = 'house_outside' .. key,
|
||
|
heading = 90.0,
|
||
|
debugPoly = Config.ZoneDebug,
|
||
|
minZ = enterCoords.z - 15.0,
|
||
|
maxZ = enterCoords.z + 5.0,
|
||
|
}, {
|
||
|
options = options,
|
||
|
distance = 2.5
|
||
|
})
|
||
|
table.insert(self.zones, 'house_outside' .. key)
|
||
|
end
|
||
|
|
||
|
function Target:initExit(key)
|
||
|
local houseData = self.houses[key]
|
||
|
local exitCoords
|
||
|
if houseData.mlo then return end
|
||
|
if houseData.ipl then
|
||
|
exitCoords = vec3(houseData.ipl.exit.x, houseData.ipl.exit.y, houseData.ipl.exit.z)
|
||
|
else
|
||
|
if not houseData.coords.exit then return end
|
||
|
exitCoords = vec3(houseData.coords.exit.x, houseData.coords.exit.y, houseData.coords.exit.z)
|
||
|
end
|
||
|
exports[target_name]:AddBoxZone('house_exit' .. key, exitCoords, Config.TargetLength, Config.TargetWidth, {
|
||
|
name = 'house_exit' .. key,
|
||
|
heading = 90.0,
|
||
|
debugPoly = Config.ZoneDebug,
|
||
|
minZ = exitCoords.z - 15.0,
|
||
|
maxZ = exitCoords.z + 5.0,
|
||
|
}, {
|
||
|
options = {
|
||
|
{
|
||
|
icon = 'fa-solid fa-door-open',
|
||
|
label = Lang('HOUSING_TARGET_EXIT_HOUSE'),
|
||
|
action = function()
|
||
|
if houseData.ipl then
|
||
|
LeaveIplHouse(EnteredHouse, inOwned)
|
||
|
else
|
||
|
LeaveHouse()
|
||
|
end
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
{
|
||
|
icon = 'fa-solid fa-bell',
|
||
|
label = Lang('HOUSING_TARGET_RING_DOORBELL'),
|
||
|
action = function()
|
||
|
TriggerServerEvent('qb-houses:server:OpenDoor', CurrentDoorBell, CurrentHouse)
|
||
|
CurrentDoorBell = 0
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
return CurrentDoorBell ~= 0
|
||
|
end,
|
||
|
},
|
||
|
{
|
||
|
icon = 'fa-solid fa-video',
|
||
|
label = Lang('HOUSING_TARGET_ACCESS_CAMERA'),
|
||
|
action = function()
|
||
|
FrontDoorCam(houseData.coords.enter)
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
if houseData.ipl then return false end
|
||
|
return not inOwned
|
||
|
end,
|
||
|
},
|
||
|
},
|
||
|
distance = 2.5
|
||
|
})
|
||
|
table.insert(self.zones, 'house_exit' .. key)
|
||
|
end
|
||
|
|
||
|
function Target:initWardrobe()
|
||
|
local wardrobe = CurrentHouseData.wardrobe
|
||
|
if not wardrobe then return Debug('Target:initWardrobe ::: No wardrobe coords') end
|
||
|
exports[target_name]:AddBoxZone('house_wardrobe', wardrobe, Config.TargetLength, Config.TargetWidth, {
|
||
|
name = 'house_wardrobe',
|
||
|
heading = 90.0,
|
||
|
debugPoly = Config.ZoneDebug,
|
||
|
minZ = wardrobe.z - 15.0,
|
||
|
maxZ = wardrobe.z + 5.0,
|
||
|
}, {
|
||
|
options = {
|
||
|
{
|
||
|
icon = 'fa-solid fa-magnifying-glass',
|
||
|
label = Lang('HOUSING_TARGET_WARDROBE_INTERACTION'),
|
||
|
action = function()
|
||
|
openWardrobe()
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
},
|
||
|
distance = 2.5
|
||
|
})
|
||
|
end
|
||
|
|
||
|
function Target:initStash()
|
||
|
local stash = CurrentHouseData.stash
|
||
|
if not stash then return Debug('Target:initStash ::: No stash coords') end
|
||
|
exports[target_name]:AddBoxZone('house_stash', stash, Config.TargetLength, Config.TargetWidth, {
|
||
|
name = 'house_stash',
|
||
|
heading = 90.0,
|
||
|
debugPoly = Config.ZoneDebug,
|
||
|
minZ = stash.z - 15.0,
|
||
|
maxZ = stash.z + 5.0,
|
||
|
}, {
|
||
|
options = {
|
||
|
{
|
||
|
icon = 'fa-solid fa-magnifying-glass',
|
||
|
label = Lang('HOUSING_TARGET_STASH_INTERACTION'),
|
||
|
action = function()
|
||
|
if CanAccessStash() then
|
||
|
openStash()
|
||
|
end
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
{
|
||
|
icon = 'fa-solid fa-key',
|
||
|
label = Lang('HOUSING_MENU_VAULT_SET_CODE'),
|
||
|
action = function()
|
||
|
OpenVaultCodeMenu()
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
if not CurrentHouseData.isOfficialOwner then return false end
|
||
|
local houseData = Config.Houses[CurrentHouse]
|
||
|
return table.includes(houseData.upgrades, 'vault')
|
||
|
end,
|
||
|
}
|
||
|
},
|
||
|
distance = 2.5
|
||
|
})
|
||
|
end
|
||
|
|
||
|
function Target:initLogout()
|
||
|
local logout = CurrentHouseData.logout
|
||
|
if not logout then return Debug('Target:initLogout ::: No logout coords') end
|
||
|
exports[target_name]:AddBoxZone('house_logout', logout, Config.TargetLength, Config.TargetWidth, {
|
||
|
name = 'house_logout',
|
||
|
heading = 90.0,
|
||
|
debugPoly = Config.ZoneDebug,
|
||
|
minZ = logout.z - 15.0,
|
||
|
maxZ = logout.z + 5.0,
|
||
|
}, {
|
||
|
options = {
|
||
|
{
|
||
|
icon = 'fa-solid fa-magnifying-glass',
|
||
|
label = Lang('HOUSING_TARGET_LOGOUT_INTERACTION'),
|
||
|
action = function()
|
||
|
DoScreenFadeOut(250)
|
||
|
while not IsScreenFadedOut() do Wait(10) end
|
||
|
DespawnInterior(HouseObj, function()
|
||
|
WeatherSyncEvent(false) -- Weather Events
|
||
|
|
||
|
local house = CurrentHouse
|
||
|
SetEntityCoords(PlayerPed, Config.Houses[house].coords.enter.x, Config.Houses[house].coords.enter.y, Config.Houses[house].coords.enter.z + 0.5)
|
||
|
SetEntityHeading(PlayerPed, Config.Houses[house].coords.enter.h)
|
||
|
inOwned = false
|
||
|
TriggerServerEvent('qb-houses:server:LogoutLocation')
|
||
|
end)
|
||
|
end,
|
||
|
canInteract = function(entity, distance, data)
|
||
|
return true
|
||
|
end,
|
||
|
},
|
||
|
},
|
||
|
distance = 2.5
|
||
|
})
|
||
|
end
|
||
|
|
||
|
function Target:init()
|
||
|
for k, v in pairs(self.zones) do
|
||
|
exports[target_name]:RemoveZone(v)
|
||
|
end
|
||
|
self.zones = {}
|
||
|
exports[target_name]:RemoveTargetModel(lastMLODoors)
|
||
|
for k, v in pairs(self.houses) do
|
||
|
self:initOutside(k)
|
||
|
self:initMLODoors(k)
|
||
|
self:initExit(k)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
function Target:initInsideInteractions()
|
||
|
exports[target_name]:RemoveZone('house_wardrobe')
|
||
|
exports[target_name]:RemoveZone('house_stash')
|
||
|
exports[target_name]:RemoveZone('house_logout')
|
||
|
Target:initWardrobe()
|
||
|
Target:initStash()
|
||
|
Target:initLogout()
|
||
|
end
|
||
|
|
||
|
function Target:formatHouses()
|
||
|
self.houses = table.filter(self.houses, function(house)
|
||
|
return not house.apartmentNumber or house.apartmentNumber == 'apt-0'
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
RegisterNetEvent('housing:initHouses', function(houseConfig)
|
||
|
Target.houses = houseConfig
|
||
|
Target:formatHouses()
|
||
|
Target:init()
|
||
|
if Target.initObjectInteractions then
|
||
|
Target:initObjectInteractions()
|
||
|
end
|
||
|
end)
|