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)
 | 
