Scripts/resources/[ps]/ps-housing/client/cl_property.lua
2024-12-29 21:28:24 +01:00

742 lines
20 KiB
Lua

Property = {
property_id = nil,
propertyData = nil,
shell = nil,
shellData = nil,
inProperty = false,
shellObj = nil,
has_access = false,
owner = false,
storageTarget = nil,
clothingTarget = nil,
furnitureObjs = {},
garageZone = nil,
doorbellPool = {},
entranceTarget = nil, -- needed for ox target
exitTarget = nil, -- needed for ox target
blip = nil,
}
Property.__index = Property
function Property:new(propertyData)
local self = setmetatable({}, Property)
self.property_id = tostring(propertyData.property_id)
-- Remove furnitures from property data for memory purposes
propertyData.furnitures = {}
self.propertyData = propertyData
local citizenid = PlayerData.citizenid
self.owner = propertyData.owner == citizenid
self.has_access = lib.table.contains(self.propertyData.has_access, citizenid)
if propertyData.apartment then
local aptName = propertyData.apartment
local apartment = ApartmentsTable[aptName]
if not apartment and Config.Apartments[aptName] then
ApartmentsTable[aptName] = Apartment:new(Config.Apartments[aptName])
apartment = ApartmentsTable[aptName]
elseif not apartment then
Debug(aptName .. " not found in Config")
return
end
apartment:AddProperty(self.property_id)
else
self:RegisterPropertyEntrance()
self:RegisterGarageZone()
end
return self
end
function Property:GetDoorCoords()
local coords = nil
local dataApartment = self.propertyData.apartment
if dataApartment then
local apartment = dataApartment
coords = Config.Apartments[apartment].door
else
coords = self.propertyData.door_data
end
return coords
end
function Property:CreateShell()
local coords = self:GetDoorCoords()
coords = vec3(coords.x, coords.y, coords.z - 25.0)
self.shell = Shell:CreatePropertyShell(self.propertyData.shell, coords)
self.shellObj = self.shell.entity
local doorOffset = self.shellData.doorOffset
local offset = GetOffsetFromEntityInWorldCoords(self.shellObj, doorOffset.x, doorOffset.y, doorOffset.z)
self:RegisterDoorZone(offset)
SetEntityCoordsNoOffset(cache.ped, offset.x, offset.y, offset.z, false, false, true)
SetEntityHeading(cache.ped, self.shellData.doorOffset.h)
end
function Property:RegisterDoorZone(offset)
local function leave()
self:LeaveShell()
end
local function checkDoor()
self:OpenDoorbellMenu()
end
local coords = offset
local size = vector3(1.0, self.shellData.doorOffset.width, 3.0)
local heading = self.shellData.doorOffset.h
self.exitTarget = Framework[Config.Target].AddDoorZoneInside(coords, size, heading, leave, checkDoor)
end
function Property:RegisterPropertyEntrance()
local door = self.propertyData.door_data
local size = vector3(door.length, door.width, 2.5)
local heading = door.h
--Can be anon functions but I like to keep them named its more readable
local function enter()
TriggerServerEvent("ps-housing:server:enterProperty", self.property_id)
end
local function raid()
TriggerServerEvent("ps-housing:server:raidProperty", self.property_id)
end
local function showcase()
TriggerServerEvent("ps-housing:server:showcaseProperty", self.property_id)
end
local function showData()
local data = lib.callback.await("ps-housing:cb:getPropertyInfo", source, self.property_id)
if not data then return end
local content = "**Ejer:** " .. data.owner .. " \n" .. "**Beskrivelse:** " .. data.description .. " \n" .. "**Gade:** " .. data.street .. " \n" .. "**Region:** " .. data.region .. " \n" .. "**Interiør:** " .. data.shell .. " \n" .. "**Til salg:** " .. (data.for_sale and "Ja" or "Nej")
if data.for_sale then
content = content .. " \n" .. "**Pris:** " .. data.price.. ",-"
end
lib.alertDialog({
header = data.street .. " " .. data.property_id,
content = content,
centered = true,
})
end
local targetName = string.format("%s_%s", self.propertyData.street, self.property_id)
self.entranceTarget = Framework[Config.Target].AddEntrance(door, size, heading, self.property_id, enter, raid, showcase, showData, targetName)
if self.owner or self.has_access then
self:CreateBlip()
end
end
function Property:UnregisterPropertyEntrance()
if not self.entranceTarget then return end
Framework[Config.Target].RemoveTargetZone(self.entranceTarget)
self.entranceTarget = nil
end
function Property:RegisterGarageZone()
if not next(self.propertyData.garage_data) then return end
if not (self.has_access or self.owner) or not self.owner then
return
end
local garageData = self.propertyData.garage_data
local garageName = string.format("property-%s-garage", self.property_id)
local data = {
takeVehicle = {
x = garageData.x,
y = garageData.y,
z = garageData.z,
w = garageData.h
},
type = "house",
label = self.propertyData.street .. self.property_id .. " Garage",
}
TriggerEvent("qb-garages:client:addHouseGarage", self.property_id, data)
self.garageZone = lib.zones.box({
coords = vec3(garageData.x, garageData.y, garageData.z),
size = vector3(garageData.length + 5.0, garageData.width + 5.0, 3.5),
rotation = garageData.h,
debug = Config.DebugMode,
onEnter = function()
TriggerEvent('qb-garages:client:setHouseGarage', self.property_id, true)
end,
})
end
function Property:UnregisterGarageZone()
if not self.garageZone then return end
TriggerEvent("qb-garages:client:removeHouseGarage", self.property_id)
self.garageZone:remove()
self.garageZone = nil
end
function Property:EnterShell()
DoScreenFadeOut(250)
TriggerServerEvent("InteractSound_SV:PlayOnSource", "houses_door_open", 0.25)
Wait(250)
self.inProperty = true
self.shellData = Config.Shells[self.propertyData.shell]
self:CreateShell()
self:LoadFurnitures()
self:GiveMenus()
Wait(250)
DoScreenFadeIn(250)
end
function Property:LeaveShell()
if not self.inProperty then return end
DoScreenFadeOut(250)
TriggerServerEvent("InteractSound_SV:PlayOnSource", "houses_door_open", 0.25)
Wait(250)
local coords = self:GetDoorCoords()
SetEntityCoordsNoOffset(cache.ped, coords.x, coords.y, coords.z, false, false, true)
TriggerServerEvent("ps-housing:server:leaveProperty", self.property_id)
self:UnloadFurnitures()
self.propertyData.furnitures = {}
self.shell:DespawnShell()
self.shell = nil
if self.exitTarget then
Framework[Config.Target].RemoveTargetZone(self.exitTarget)
self.exitTarget = nil
end
self:RemoveBlip()
self:RemoveMenus()
self.doorbellPool = {}
self.inProperty = false
Wait(250)
DoScreenFadeIn(250)
end
function Property:GiveMenus()
if not self.inProperty then return end
local accessAndConfig = self.has_access and Config.AccessCanEditFurniture
if self.owner or accessAndConfig then
Framework[Config.Radial].AddRadialOption(
"furniture_menu",
"Indretning",
"house-user",
function()
Modeler:OpenMenu(self.property_id)
end,
"ps-housing:client:openFurnitureMenu",
{ propertyId = self.property_id }
)
end
if self.owner then
Framework[Config.Radial].AddRadialOption(
"access_menu",
"Ejendomsadgang",
"key",
function()
self:ManageAccessMenu()
end,
"ps-housing:client:openManagePropertyAccessMenu",
{ propertyId = self.property_id }
)
end
end
function Property:RemoveMenus()
if not self.inProperty then return end
Framework[Config.Radial].RemoveRadialOption("furniture_menu")
if self.owner then
Framework[Config.Radial].RemoveRadialOption("access_menu")
end
end
function Property:ManageAccessMenu()
if not self.inProperty then return end
if not self.owner then
Framework[Config.Notify].Notify("Kun ejeren kan gøre dette.", "error")
return
end
--Fuck qb-menu
local id = "property-" .. self.property_id .. "-access"
local menu = {
id = id,
title = "Administrer adgang",
options = {},
}
menu.options[#menu.options + 1] = {
title = "Giv Adgang",
onSelect = function()
self:GiveAccessMenu()
end,
}
menu.options[#menu.options + 1] = {
title = "Fjern Adgang",
onSelect = function()
self:RevokeAccessMenu()
end,
}
lib.registerContext(menu)
lib.showContext(id)
end
function Property:GiveAccessMenu()
if not self.inProperty then return end
if not self.owner then
return
end
local id = "property-" .. self.property_id .. "-access-give"
local menu = {
id = id,
title = "Giv Adgang",
options = {},
}
local players = lib.callback.await("ps-housing:cb:getPlayersInProperty", source, self.property_id) or {}
if #players > 0 then
for i = 1, #players do
local v = players[i]
menu.options[#menu.options + 1] = {
title = v.name,
description = "Giv Adgang",
onSelect = function()
TriggerServerEvent("ps-housing:server:addAccess", self.property_id, v.src)
end,
}
end
lib.registerContext(menu)
lib.showContext(id)
else
Framework[Config.Notify].Notify("Der er ingen i boligen", "error")
end
end
function Property:RevokeAccessMenu()
if not self.owner then
return
end
local id = "property-" .. self.property_id .. "-access-already"
local alreadyAccessMenu = {
id = id,
title = "Revoke Access",
options = {},
}
local playersWithAccess = lib.callback.await("ps-housing:cb:getPlayersWithAccess", source, self.property_id) or {}
-- only stores names and citizenids in a table so if their offline you can still remove them
if #playersWithAccess > 0 then
for i = 1, #playersWithAccess do
local v = playersWithAccess[i]
alreadyAccessMenu.options[#alreadyAccessMenu.options + 1] = {
title = v.name,
description = "Fjern Adgang",
onSelect = function()
TriggerServerEvent("ps-housing:server:removeAccess", self.property_id, v.citizenid)
end,
}
end
lib.registerContext(alreadyAccessMenu)
lib.showContext(id)
else
Framework[Config.Notify].Notify("Ingen har adgang til denne ejendom", "error")
end
end
function Property:OpenDoorbellMenu()
if not self.inProperty then return end
if not next(self.doorbellPool) then
Framework[Config.Notify].Notify("Der er ingen ved døren", "error")
return
end
local id = string.format("property-%s-doorbell", self.property_id)
local menu = {
id = id,
title = "Gæster ved døren",
options = {},
}
for k, v in pairs(self.doorbellPool) do
menu.options[#menu.options + 1] = {
title = v.name,
onSelect = function()
TriggerServerEvent(
"ps-housing:server:doorbellAnswer",
{ targetSrc = v.src, property_id = self.property_id }
)
end,
}
end
lib.registerContext(menu)
lib.showContext(id)
end
function Property:LoadFurniture(furniture)
local coords = GetOffsetFromEntityInWorldCoords(self.shellObj, furniture.position.x, furniture.position.y, furniture.position.z)
local hash = furniture.object
lib.requestModel(hash)
local entity = CreateObjectNoOffset(hash, coords.x, coords.y, coords.z, false, true, false)
SetModelAsNoLongerNeeded(hash)
SetEntityRotation(entity, furniture.rotation.x, furniture.rotation.y, furniture.rotation.z, 2, true)
if furniture.type == 'door' and Config.DynamicDoors then
Debug("Object: "..furniture.label.." wont be frozen")
else
FreezeEntityPosition(entity, true)
end
if furniture.type and Config.FurnitureTypes[furniture.type] then
Config.FurnitureTypes[furniture.type](entity, self.property_id, self.propertyData.shell)
end
self.furnitureObjs[#self.furnitureObjs + 1] = {
entity = entity,
id = furniture.id,
label = furniture.label,
object = furniture.object,
position = {
x = coords.x,
y = coords.y,
z = coords.z,
},
rotation = furniture.rotation,
type = furniture.type,
}
end
function Property:LoadFurnitures()
self.propertyData.furnitures = lib.callback.await('ps-housing:cb:getFurnitures', source, self.property_id) or {}
for i = 1, #self.propertyData.furnitures do
local furniture = self.propertyData.furnitures[i]
self:LoadFurniture(furniture)
end
end
function Property:UnloadFurniture(furniture, index)
local entity = furniture?.entity
if not entity then
for i = 1, #self.furnitureObjs do
if self.furnitureObjs[i]?.id and furniture?.id and self.furnitureObjs[i].id == furniture.id then
entity = self.furnitureObjs[i]?.entity
break
end
end
end
if self.clothingTarget == entity or self.storageTarget == entity then
Framework[Config.Target].RemoveTargetEntity(entity)
if self.clothingTarget == entity then
self.clothingTarget = nil
elseif self.storageTarget == entity then
self.storageTarget = nil
end
end
if index and self.furnitureObjs?[index] then
table.remove(self.furnitureObjs, index)
else
for i = 1, #self.furnitureObjs do
if self.furnitureObjs[i]?.id and furniture?.id and self.furnitureObjs[i].id == furniture.id then
table.remove(self.furnitureObjs, i)
break
end
end
end
DeleteObject(entity)
end
function Property:UnloadFurnitures()
for i = 1, #self.furnitureObjs do
local furniture = self.furnitureObjs[i]
self:UnloadFurniture(furniture, i)
end
self.furnitureObjs = {}
end
function Property:CreateBlip()
local door_data = self.propertyData.door_data
local blip = AddBlipForCoord(door_data.x, door_data.y, door_data.z)
if self.propertyData.garage_data.x ~= nil then
SetBlipSprite(blip, 492)
else
SetBlipSprite(blip, 40)
end
SetBlipScale(blip, 0.8)
SetBlipColour(blip, 2)
SetBlipAsShortRange(blip, true)
BeginTextCommandSetBlipName("STRING")
AddTextComponentString(self.propertyData.street .. " " .. self.property_id)
EndTextCommandSetBlipName(blip)
self.blip = blip
end
function Property:RemoveBlip()
if not self.blip then return end
RemoveBlip(self.blip)
self.blip = nil
end
function Property:RemoveProperty()
local targetName = string.format("%s_%s", self.propertyData.street, self.property_id)
Framework[Config.Target].RemoveTargetZone(targetName)
self:RemoveBlip()
self:LeaveShell()
--@@ comeback to this
-- Think it works now
if self.propertyData.apartment then
ApartmentsTable[self.propertyData.apartment]:RemoveProperty()
end
self = nil
end
local function findFurnitureDifference(new, old)
local added = {}
local removed = {}
for i = 1, #new do
local found = false
for j = 1, #old do
if new[i].id == old[j].id then
found = true
break
end
end
if not found then
added[#added + 1] = new[i]
end
end
for i = 1, #old do
local found = false
for j = 1, #new do
if old[i].id == new[j].id then
found = true
break
end
end
if not found then
removed[#removed + 1] = old[i]
end
end
return added, removed
end
-- I think this whole furniture sync is a bit shit, but I cbf thinking
function Property:UpdateFurnitures(newFurnitures)
if not self.inProperty then return end
local oldFurnitures = self.propertyData.furnitures
local added, removed = findFurnitureDifference(newFurnitures, oldFurnitures)
for i = 1, #added do
local furniture = added[i]
self:LoadFurniture(furniture)
end
for i = 1, #removed do
local furniture = removed[i]
self:UnloadFurniture(furniture)
end
self.propertyData.furnitures = newFurnitures
Modeler:UpdateFurnitures()
end
function Property:UpdateDescription(newDescription)
self.propertyData.description = newDescription
end
function Property:UpdatePrice(newPrice)
self.propertyData.price = newPrice
end
function Property:UpdateForSale(forSale)
self.propertyData.for_sale = forSale
end
function Property:UpdateShell(newShell)
self:LeaveShell()
self.propertyData.shell = newShell
if self.inProperty then
self:EnterShell()
end
end
function Property:UpdateOwner(newOwner)
self.propertyData.owner = newOwner
local citizenid = PlayerData.citizenid
self.owner = newOwner == citizenid
self:UnregisterGarageZone()
self:RegisterGarageZone()
self:CreateBlip()
if not self.inProperty then return end
self:RemoveMenus()
self:GiveMenus()
end
function Property:UpdateImgs(newImgs)
self.propertyData.imgs = newImgs
end
function Property:UpdateDoor(newDoor, newStreet, newRegion)
self.propertyData.door_data = newDoor
self.propertyData.street = newStreet
self.propertyData.region = newRegion
self:UnregisterPropertyEntrance()
self:RegisterPropertyEntrance()
end
function Property:UpdateHas_access(newHas_access)
local citizenid = PlayerData.citizenid
self.propertyData.has_access = newHas_access
self.has_access = lib.table.contains(newHas_access, citizenid)
if not self.inProperty then return end
self:RemoveMenus()
self:GiveMenus()
end
function Property:UpdateGarage(newGarage)
self.propertyData.garage_data = newGarage
self:UnregisterGarageZone()
self:RegisterGarageZone()
end
function Property:UpdateApartment(newApartment)
self:LeaveShell()
local oldAptName = self.propertyData.apartment
local oldApt = ApartmentsTable[oldAptName]
if oldApt then
oldApt:RemoveProperty(self.property_id)
end
self.propertyData.apartment = newApartment
local newApt = ApartmentsTable[newApartment]
if newApt then
newApt:AddProperty(self.property_id)
end
TriggerEvent("ps-housing:client:updateApartment", oldAptName, newApartment)
end
function Property.Get(property_id)
return PropertiesTable[tostring(property_id)]
end
RegisterNetEvent("ps-housing:client:enterProperty", function(property_id)
local property = Property.Get(property_id)
if property ~= nil then
property:EnterShell()
end
end)
RegisterNetEvent("ps-housing:client:updateDoorbellPool", function(property_id, data)
local property = Property.Get(property_id)
property.doorbellPool = data
end)
RegisterNetEvent("ps-housing:client:updateFurniture", function(property_id, furnitures)
local property = Property.Get(property_id)
if not property then return end
property:UpdateFurnitures(furnitures)
end)
RegisterNetEvent("ps-housing:client:updateProperty", function(type, property_id, data)
local property = Property.Get(property_id)
if not property then return end
property[type](property, data)
TriggerEvent("ps-housing:client:updatedProperty", property_id)
end)
RegisterNetEvent("ps-housing:client:openFurnitureMenu", function(data)
Modeler:OpenMenu(data.options.propertyId)
end)
RegisterNetEvent("ps-housing:client:openManagePropertyAccessMenu", function(data)
local property = Property.Get(data.options.propertyId)
if not property then return end
property:ManageAccessMenu()
end)