461 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			Lua
		
	
	
	
	
	
			
		
		
	
	
			461 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)
 |