408 lines
18 KiB
Lua
408 lines
18 KiB
Lua
--[[
|
|
ESX Property - Properties Made Right!
|
|
Copyright (C) 2025 ESX-Framework
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
]]
|
|
if Config.Furniture.Enabled then
|
|
SpawnedFurniture = {}
|
|
RegisterNetEvent("esx_property:syncFurniture", function(PropertyId, Furniture)
|
|
if Properties[PropertyId] and Furniture then
|
|
Properties[PropertyId].furniture = Furniture
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent("esx_property:placeFurniture", function(Id, furniture, findex)
|
|
if Properties[Id] and InProperty and CurrentId == Id then
|
|
ESX.Game.SpawnLocalObject(furniture.Name, vector3(furniture.Pos.x, furniture.Pos.y, furniture.Pos.z - 0.1), function(object)
|
|
SetEntityCoordsNoOffset(object, furniture.Pos.x, furniture.Pos.y, furniture.Pos.z)
|
|
SetEntityHeading(object, furniture.Heading)
|
|
SetEntityAsMissionEntity(object, true, true)
|
|
FreezeEntityPosition(object, true)
|
|
SpawnedFurniture[findex] = {obj = object, data = furniture}
|
|
end)
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent("esx_property:removeFurniture", function(Id, furniture)
|
|
if Properties[Id] and InProperty and CurrentId == Id then
|
|
if SpawnedFurniture[furniture] then
|
|
DeleteEntity(SpawnedFurniture[furniture].obj)
|
|
SpawnedFurniture[furniture] = nil
|
|
end
|
|
end
|
|
end)
|
|
|
|
RegisterNetEvent("esx_property:editFurniture", function(Id, furniture, Pos, Heading)
|
|
if Properties[Id] and InProperty and CurrentId == Id then
|
|
|
|
if SpawnedFurniture[furniture] then
|
|
local obj = SpawnedFurniture[furniture].obj
|
|
SetEntityCoordsNoOffset(obj, Pos.x, Pos.y, Pos.z)
|
|
SetEntityHeading(obj, Heading)
|
|
SpawnedFurniture[furniture].data.Pos = Pos
|
|
SpawnedFurniture[furniture].data.Heading = Heading
|
|
end
|
|
end
|
|
end)
|
|
|
|
------------------------ Furniture Start -------------------------------
|
|
local inFurniture = false
|
|
local CurrentlyEditing = nil
|
|
|
|
function TempFurniturePlacement(PropertyId, PropName, PropIndex, PropCatagory, PropPrice, Existing, Pos, Heading)
|
|
ESX.CloseContext()
|
|
if CurrentlyEditing then
|
|
DeleteEntity(CurrentlyEditing)
|
|
CurrentlyEditing = nil
|
|
end
|
|
local function InstructionButtonMessage(text)
|
|
BeginTextCommandScaleformString("STRING")
|
|
AddTextComponentScaleform(text)
|
|
EndTextCommandScaleformString()
|
|
end
|
|
|
|
local function CreateInstuctionScaleform(scaleform)
|
|
local scaleform = RequestScaleformMovie(scaleform)
|
|
while not HasScaleformMovieLoaded(scaleform) do
|
|
Wait(0)
|
|
end
|
|
PushScaleformMovieFunction(scaleform, "CLEAR_ALL")
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_CLEAR_SPACE")
|
|
PushScaleformMovieFunctionParameterInt(200)
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT")
|
|
PushScaleformMovieFunctionParameterInt(1)
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.MinusX, true))
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.PlusX, true))
|
|
InstructionButtonMessage(TranslateCap("rot_left_right"))
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT")
|
|
PushScaleformMovieFunctionParameterInt(3)
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.MinusY, true))
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.PlusY, true))
|
|
InstructionButtonMessage(TranslateCap("rot_up_down"))
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT")
|
|
PushScaleformMovieFunctionParameterInt(5)
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Up, true))
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Down, true))
|
|
InstructionButtonMessage(TranslateCap("height"))
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT")
|
|
PushScaleformMovieFunctionParameterInt(7)
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.RotateLeft, true))
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.RotateRight, true))
|
|
InstructionButtonMessage(TranslateCap("rotate"))
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT")
|
|
PushScaleformMovieFunctionParameterInt(9)
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Confirm, true))
|
|
InstructionButtonMessage(TranslateCap("place"))
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_DATA_SLOT")
|
|
PushScaleformMovieFunctionParameterInt(0)
|
|
N_0xe83a3e3557a56640(GetControlInstructionalButton(1, Config.Furniture.Controls.Exit, true))
|
|
InstructionButtonMessage(TranslateCap("delete_furni"))
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "DRAW_INSTRUCTIONAL_BUTTONS")
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
PushScaleformMovieFunction(scaleform, "SET_BACKGROUND_COLOUR")
|
|
PushScaleformMovieFunctionParameterInt(0)
|
|
PushScaleformMovieFunctionParameterInt(0)
|
|
PushScaleformMovieFunctionParameterInt(0)
|
|
PushScaleformMovieFunctionParameterInt(80)
|
|
PopScaleformMovieFunctionVoid()
|
|
|
|
return scaleform
|
|
end
|
|
function GetCameraDirection()
|
|
local rotation = GetGameplayCamRot() * (math.pi / 180)
|
|
|
|
return vector3(-math.sin(rotation.z), math.cos(rotation.z), math.sin(rotation.x))
|
|
end
|
|
local Position = GetEntityCoords(ESX.PlayerData.ped)
|
|
local PropSpawn = Position - vector3(0.5, 0.5, 1.0)
|
|
ESX.Game.SpawnLocalObject(PropName, PropSpawn, function(CurrentlyEditing)
|
|
CurrentlyEditing = CurrentlyEditing
|
|
NetworkSetObjectForceStaticBlend(CurrentlyEditing, true)
|
|
SetEntityAsMissionEntity(CurrentlyEditing, true, true)
|
|
SetEntityCollision(CurrentlyEditing, false, true)
|
|
if Existing then
|
|
SetEntityCoordsNoOffset(CurrentlyEditing, vector3(Pos.x, Pos.y, Pos.z))
|
|
SetEntityHeading(CurrentlyEditing, Heading)
|
|
else
|
|
PlaceObjectOnGroundProperly(CurrentlyEditing)
|
|
end
|
|
FreezeEntityPosition(CurrentlyEditing, true)
|
|
while true do
|
|
HudForceWeaponWheel(false)
|
|
HideHudComponentThisFrame(19)
|
|
HideHudComponentThisFrame(20)
|
|
local CurrentPos = GetEntityCoords(CurrentlyEditing)
|
|
local CurrentHeading = GetEntityHeading(CurrentlyEditing)
|
|
local Rotation = GetEntityRotation(CurrentlyEditing, 2)
|
|
SetEntityDrawOutline(CurrentlyEditing, true)
|
|
SetEntityDrawOutlineColor(55, 140, 191, 200)
|
|
SetEntityDrawOutlineShader(1)
|
|
local instructions = CreateInstuctionScaleform("instructional_buttons")
|
|
DrawScaleformMovieFullscreen(instructions, 255, 255, 255, 255, 0)
|
|
Wait(0)
|
|
if IsControlJustPressed(0, 202) then
|
|
DeleteObject(CurrentlyEditing)
|
|
end
|
|
|
|
if IsControlPressed(0, Config.Furniture.Controls.MinusY) then
|
|
local direction = GetCameraDirection()
|
|
CurrentPos = CurrentPos - vector3(direction.x * Config.Furniture.MovementSpeed, direction.y * Config.Furniture.MovementSpeed, 0)
|
|
SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos.x, CurrentPos.y, CurrentPos.z)
|
|
end
|
|
|
|
if IsControlPressed(0, Config.Furniture.Controls.PlusY) then
|
|
local direction = GetCameraDirection()
|
|
CurrentPos = CurrentPos + vector3(direction.x * Config.Furniture.MovementSpeed, direction.y * Config.Furniture.MovementSpeed, 0)
|
|
SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos)
|
|
end
|
|
|
|
if IsControlPressed(0, Config.Furniture.Controls.Up) then
|
|
SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos.x, CurrentPos.y, CurrentPos.z + Config.Furniture.MovementZspeed)
|
|
end
|
|
|
|
if IsControlPressed(0, Config.Furniture.Controls.Down) then
|
|
SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos.x, CurrentPos.y, CurrentPos.z - Config.Furniture.MovementZspeed)
|
|
end
|
|
if IsControlPressed(0, Config.Furniture.Controls.RotateLeft) then
|
|
CurrentHeading = CurrentHeading + Config.Furniture.RotationSpeed
|
|
SetEntityRotation(CurrentlyEditing, 0, 0, CurrentHeading, 1, true)
|
|
-- SetEntityRotation(CurrentlyEditing, Rotation.x, Rotation.y, Rotation.z + Config.Furniture.RotationSpeed, 2, false)
|
|
-- SetEntityHeading(CurrentlyEditing, CurrentHeading + Config.Furniture.RotationSpeed)
|
|
end
|
|
|
|
if IsControlPressed(0, Config.Furniture.Controls.RotateRight) then
|
|
CurrentHeading = CurrentHeading - Config.Furniture.RotationSpeed
|
|
SetEntityRotation(CurrentlyEditing, 0, 0, CurrentHeading, 1, true)
|
|
-- SetEntityRotation(CurrentlyEditing, Rotation.x, Rotation.y, Rotation.z - Config.Furniture.RotationSpeed, 2, false)
|
|
-- SetEntityHeading(CurrentlyEditing, CurrentHeading - Config.Furniture.RotationSpeed)
|
|
end
|
|
|
|
if IsControlPressed(0, Config.Furniture.Controls.PlusX) then
|
|
local direction = GetCameraDirection()
|
|
CurrentPos = CurrentPos + vector3(-direction.y * Config.Furniture.MovementSpeed, direction.x * Config.Furniture.MovementSpeed, 0)
|
|
SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos)
|
|
end
|
|
|
|
if IsControlPressed(0, Config.Furniture.Controls.MinusX) then
|
|
local direction = GetCameraDirection()
|
|
CurrentPos = CurrentPos - vector3(-direction.y * Config.Furniture.MovementSpeed, direction.x * Config.Furniture.MovementSpeed, 0)
|
|
SetEntityCoordsNoOffset(CurrentlyEditing, CurrentPos)
|
|
end
|
|
|
|
if IsControlJustPressed(0, Config.Furniture.Controls.Confirm) then
|
|
if not Existing then
|
|
ESX.OpenContext("right",
|
|
{{unslectable = true, title = TranslateCap("confirm_buy",Config.FurnitureCatagories[PropCatagory][PropIndex].title),
|
|
description = TranslateCap("price",Config.FurnitureCatagories[PropCatagory][PropIndex].price), icon = "fas fa-shopping-cart"},
|
|
{title = TranslateCap("yes"), value = "buy", icon = "fas fa-check"}, {title = TranslateCap("no"), icon = "fas fa-minus"}}, function(menu, element)
|
|
if element.value and element.value == "buy" then
|
|
ESX.TriggerServerCallback("esx_property:buyFurniture", function(response)
|
|
if response then
|
|
DeleteEntity(CurrentlyEditing)
|
|
ESX.ShowNotification(TranslateCap("bought_furni", Config.FurnitureCatagories[PropCatagory][PropIndex].title), "success")
|
|
ESX.CloseContext()
|
|
else
|
|
ESX.ShowNotification(TranslateCap("cannot_buy"), "error")
|
|
end
|
|
end, CurrentId, PropName, PropIndex, PropCatagory, vector3(CurrentPos.x, CurrentPos.y, CurrentPos.z), CurrentHeading)
|
|
end
|
|
end)
|
|
else
|
|
ESX.TriggerServerCallback("esx_property:editFurniture", function(response)
|
|
if response then
|
|
DeleteEntity(CurrentlyEditing)
|
|
ESX.ShowNotification(TranslateCap("edited_furni"), "success")
|
|
ESX.CloseContext()
|
|
else
|
|
ESX.ShowNotification(TranslateCap("cannot_edit"), "error")
|
|
end
|
|
end, CurrentId, PropIndex, vector3(CurrentPos.x, CurrentPos.y, CurrentPos.z), CurrentHeading)
|
|
end
|
|
end
|
|
|
|
if DoesEntityExist(CurrentlyEditing) then
|
|
inFurniture = true
|
|
else
|
|
inFurniture = false
|
|
for _, Control in pairs(Config.Furniture.Controls) do
|
|
EnableControlAction(0, Control, true)
|
|
end
|
|
break
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
function FurnitureItems(house, Store, StoreIndex, Catagory)
|
|
local Items = {{unselectable = true, title = TranslateCap("store_title", Catagory), icon = "fas fa-store"},
|
|
{title = TranslateCap("back"), value = "go-back", icon = "fas fa-arrow-left"}}
|
|
|
|
for k, v in pairs(Config.FurnitureCatagories[Catagory]) do
|
|
table.insert(Items, {title = v.title, value = v.name, index = k, description = _U("price", v.price), icon = "fas fa-shopping-cart"})
|
|
end
|
|
|
|
ESX.OpenContext("right", Items, function(data, element)
|
|
if element.value == "go-back" then
|
|
FurnitureCatagories(house, Store, StoreIndex)
|
|
else
|
|
TempFurniturePlacement(house, element.value, element.index, Catagory)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function FurnitureCatagories(house, Store, StoreIndex)
|
|
local Catagories = {{unselectable = true, title = TranslateCap("store_title", Store), icon = "fas fa-store"},
|
|
{title = TranslateCap("back"), value = "go-back", icon = "fas fa-arrow-left"}}
|
|
|
|
for k, v in pairs(Config.FurnitureStores[StoreIndex].Catagories) do
|
|
table.insert(Catagories, {title = v})
|
|
end
|
|
|
|
ESX.OpenContext("right", Catagories, function(data, element)
|
|
if element.value == "go-back" then
|
|
FurnitureStores(house)
|
|
else
|
|
FurnitureItems(house, Store, StoreIndex, element.title)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function FurnitureEdit(propertyId, furniture)
|
|
local furni = SpawnedFurniture[furniture]
|
|
local Funiture = Config.FurnitureCatagories[furni.data.Catagory][furni.data.Index]
|
|
local Catagories = {{unselectable = true, title = TranslateCap("edit_title",Funiture.title), icon = "fas fa-store"},
|
|
{title = TranslateCap("back"), value = "go-back", icon = "fas fa-arrow-left"},
|
|
{title = TranslateCap("move_title"), value = "rotate", icon = "fas fa-sync-alt"}, {title = TranslateCap("delete_furni"), value = "delete", icon = "fas fa-trash-alt"}}
|
|
|
|
ESX.OpenContext("right", Catagories, function(data, element)
|
|
if element.value == "go-back" then
|
|
FurnitureEditSelect(propertyId)
|
|
end
|
|
if element.value == "delete" then
|
|
ESX.TriggerServerCallback("esx_property:deleteFurniture", function(response)
|
|
if response then
|
|
ESX.ShowNotification(TranslateCap("delete_confirm", Funiture.title), "success")
|
|
FurnitureEditSelect(propertyId)
|
|
else
|
|
ESX.ShowNotification(TranslateCap("delete_error", Funiture.title), "error")
|
|
end
|
|
end, propertyId, furniture)
|
|
end
|
|
if element.value == "rotate" then
|
|
ESX.CloseContext()
|
|
TempFurniturePlacement(propertyId, furni.data.Name, furniture, furni.data.Catagory, furni.data.Index, true, furni.data.Pos, furni.data.Heading)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function FurnitureEditSelect(propertyId)
|
|
local Furni = {{unselectable = true, title = TranslateCap("owned_furni"), icon = "far fa-edit"},
|
|
{title = TranslateCap("back"), value = "go-back", icon = "fas fa-arrow-left"}}
|
|
|
|
for k, v in pairs(SpawnedFurniture) do
|
|
local Funiture = Config.FurnitureCatagories[v.data.Catagory][v.data.Index]
|
|
table.insert(Furni, {title = Funiture.title, value = k})
|
|
end
|
|
|
|
ESX.OpenContext("right", Furni, function(data, element)
|
|
if element.value == "go-back" then
|
|
FurnitureMenu(propertyId)
|
|
else
|
|
FurnitureEdit(propertyId, element.value)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function FurnitureStores(house)
|
|
local Options = {{unselectable = true, title = TranslateCap("menu_stores"), icon = "fas fa-store"},
|
|
{title = TranslateCap("back"), value = "go-back", icon = "fas fa-arrow-left"}}
|
|
|
|
for k, v in pairs(Config.FurnitureStores) do
|
|
table.insert(Options, {title = v.title, Store = k})
|
|
end
|
|
|
|
ESX.OpenContext("right", Options, function(data, element)
|
|
if element.Store then
|
|
FurnitureCatagories(house, element.title, element.Store)
|
|
elseif element.value == "go-back" then
|
|
FurnitureMenu(house)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function FurnitureMenu(PropertyId)
|
|
local Options = {{unselectable = true, title = TranslateCap("furni_management"), icon = "fas fa-lamp"},
|
|
{title = TranslateCap("menu_stores"), description = TranslateCap("menu_stores_desc"), icon = "fas fa-dollar-sign", value = "store"}}
|
|
|
|
if #SpawnedFurniture > 0 then
|
|
Options[#Options + 1] = {title = TranslateCap("menu_reset"), description = TranslateCap("menu_reset_desc"), icon = "fas fa-trash", value = "reset"}
|
|
Options[#Options + 1] = {title = TranslateCap("menu_edit"), description = TranslateCap("menu_edit_desc"), icon = "fas fa-archive", value = "edit"}
|
|
|
|
end
|
|
ESX.OpenContext("right", Options, function(data, element)
|
|
if element.value then
|
|
if element.value == "store" then
|
|
FurnitureStores(PropertyId)
|
|
elseif element.value == "edit" then
|
|
FurnitureEditSelect(PropertyId)
|
|
elseif element.value == "reset" then
|
|
ESX.TriggerServerCallback("esx_property:RemoveAllfurniture", function(Removed)
|
|
if Removed then
|
|
ESX.ShowNotification(TranslateCap("furni_reset_success"), "success")
|
|
else
|
|
ESX.ShowNotification(TranslateCap("furni_reset_error"), "error")
|
|
end
|
|
end, PropertyId)
|
|
end
|
|
end
|
|
end)
|
|
end
|
|
|
|
ESX.RegisterInput(TranslateCap("furni_command"), TranslateCap("furni_command_desc"), "keyboard", "M", function()
|
|
if InProperty then
|
|
ESX.TriggerServerCallback('esx_property:CanOpenFurniture', function(Access)
|
|
if Access then
|
|
if not inFurniture then
|
|
FurnitureMenu(CurrentId)
|
|
else
|
|
FurnitureStores(CurrentId)
|
|
end
|
|
else
|
|
ESX.ShowNotification(TranslateCap("furni_command_permission"), 5000, "error")
|
|
end
|
|
end, CurrentId)
|
|
else
|
|
return
|
|
end
|
|
end)
|
|
end
|