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)