920 lines
32 KiB
Lua
920 lines
32 KiB
Lua
local disablePlayerFiring = DisablePlayerFiring
|
|
local isControlJustPressed = IsControlJustPressed
|
|
local isDisabledControlJustPressed = IsDisabledControlJustPressed
|
|
local isControlJustReleased = IsControlJustReleased
|
|
local isDisabledControlJustReleased = IsDisabledControlJustReleased
|
|
local getCursorHitCoords = Utils.getCursorHitCoords
|
|
|
|
local decorateState = {
|
|
active = false,
|
|
currentObject = nil,
|
|
hide = false,
|
|
focus = false,
|
|
keepInput = false,
|
|
objects = {},
|
|
currentPage = "dynamic",
|
|
mode = "mgizmo",
|
|
freeCamera = false,
|
|
cameraFocus = false
|
|
}
|
|
|
|
local decorateMeta = {}
|
|
decorateMeta.__index = function(_, key) return decorateState[key] end
|
|
decorateMeta.__newindex = function(_, key, value)
|
|
decorateState[key] = value
|
|
|
|
if key == "currentObject" then
|
|
if value then
|
|
if decorateState.currentPage == "stash" then
|
|
SendReactMessage("select_stash_item", value.stashId)
|
|
end
|
|
if DoesEntityExist(value.handle) then
|
|
decorate:selectEntity(value.handle)
|
|
else
|
|
decorate:deselectEntity()
|
|
end
|
|
else
|
|
decorate:deselectEntity()
|
|
end
|
|
end
|
|
end
|
|
|
|
decorate = setmetatable({}, decorateMeta)
|
|
|
|
local CAMERA_LERP_SPEED = 0.01
|
|
|
|
function decorate.instructional(self, extraActions)
|
|
if not self.active then return end
|
|
|
|
if DrawingInstructional then
|
|
DrawingInstructional = false
|
|
end
|
|
|
|
local actions = {
|
|
{ key = "place_object_on_ground", label = "Place Object on Ground" },
|
|
{ key = "toggle_cursor", label = "Toggle Cursor" },
|
|
{ key = "toggle_free_mode", label = "Toggle Free Mode" },
|
|
{ key = "toggle_editor_mode", label = "Toggle Editor Mode" },
|
|
{ key = "toggle_gizmo_mode", label = "Toggle Gizmo Mode" },
|
|
{ key = "toggle_free_camera", label = "Toggle Free Camera" }
|
|
}
|
|
|
|
local currentObj = self.currentObject
|
|
if not (currentObj and currentObj.stashId) then
|
|
actions[#actions + 1] = { key = "done", label = "Buy Object" }
|
|
end
|
|
|
|
if extraActions then
|
|
for _, action in pairs(extraActions) do
|
|
actions[#actions + 1] = action
|
|
end
|
|
end
|
|
|
|
Utils.DrawInstructional(actions)
|
|
end
|
|
|
|
function decorate.open(self)
|
|
if self.active then
|
|
return Debug("decorate:open ::: decorate is already active")
|
|
end
|
|
|
|
if not currentlyInGarage then
|
|
return Notification(i18n.t("decorate.not_in_garage"), "error")
|
|
end
|
|
|
|
local isAvailable = lib.callback.await("garages:decorate:getDecorationAvailable", false, currentlyInGarage)
|
|
if not isAvailable then
|
|
return Notification(i18n.t("decorate.decoration_not_available"), "error")
|
|
end
|
|
|
|
TriggerServerEvent("garages:decorate:updateDecorationUsedBy", currentlyInGarage, true)
|
|
|
|
self.active = true
|
|
self:toggleFreeCamera(true)
|
|
self:setFocus(true)
|
|
DisableIdleCamera(true)
|
|
|
|
SendReactMessage("toggle_decorate_menu", {
|
|
visible = true,
|
|
navigation = Config.FurnitureNavigation,
|
|
furniture = Config.Furniture,
|
|
enableShop = Config.EnableF3Shop
|
|
})
|
|
|
|
ToggleHud(false)
|
|
TriggerServerEvent("garages:fiveguard:freecam", true)
|
|
self:instructional()
|
|
self:getObjects(currentlyInGarage)
|
|
|
|
gizmo:handleCameraUpdate()
|
|
mgizmo:loop()
|
|
self:handleControls()
|
|
self:checkDistance()
|
|
end
|
|
|
|
function decorate.close(self)
|
|
if not self.active then return end
|
|
|
|
self.active = false
|
|
ToggleHud(true)
|
|
DisableIdleCamera(false)
|
|
self:removeCurrentObject()
|
|
self.active = false
|
|
|
|
Utils.RemoveInstructional()
|
|
SetNuiFocus(false, false)
|
|
SetNuiFocusKeepInput(false)
|
|
SendReactMessage("toggle_decorate_menu", { visible = false })
|
|
|
|
self.focus = false
|
|
self.keepInput = false
|
|
|
|
TriggerServerEvent("garages:fiveguard:freecam", false)
|
|
TriggerServerEvent("garages:decorate:updateDecorationUsedBy", currentlyInGarage, false)
|
|
end
|
|
|
|
function decorate.checkDistance(self)
|
|
if not Config.MaximumDistanceForDecorate then return end
|
|
|
|
local startCoords = GetEntityCoords(cache.ped)
|
|
|
|
CreateThread(function()
|
|
while self.active and currentlyInGarage do
|
|
local camCoords = GetFinalRenderedCamCoord()
|
|
local distance = #(camCoords - startCoords)
|
|
|
|
if distance > Config.MaximumDistanceForDecorate then
|
|
Notification(i18n.t("decorate.too_far"), "error")
|
|
self:close()
|
|
end
|
|
|
|
Wait(500)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function decorate.selectEntity(self, entityHandle)
|
|
local activeGizmo = (self.mode == "gizmo") and gizmo or mgizmo
|
|
|
|
if entityHandle then
|
|
if not DoesEntityExist(entityHandle) then
|
|
activeGizmo.entity = nil
|
|
activeGizmo.decorateData = nil
|
|
return
|
|
end
|
|
end
|
|
|
|
local hitEntity = entityHandle
|
|
if not hitEntity then
|
|
local hitCoords, hitEnt = getCursorHitCoords()
|
|
if hitCoords and hitEnt and hitEnt ~= 0 then
|
|
hitEntity = hitEnt
|
|
end
|
|
end
|
|
|
|
if not hitEntity then return end
|
|
|
|
if self.currentPage ~= "stash" and not entityHandle then
|
|
local currentHandle = self.currentObject and self.currentObject.handle
|
|
if currentHandle ~= hitEntity then
|
|
return Notification(i18n.t("decorate.you_cant_select_entity"), "error")
|
|
end
|
|
end
|
|
|
|
local objectData = self:getObjectData(hitEntity)
|
|
if not entityHandle and not objectData then return end
|
|
|
|
activeGizmo.entity = hitEntity
|
|
activeGizmo.decorateData = objectData
|
|
|
|
local currentHandle = self.currentObject and self.currentObject.handle
|
|
if currentHandle ~= hitEntity then
|
|
self.currentObject = {
|
|
handle = hitEntity,
|
|
modelName = objectData and objectData.modelName,
|
|
stashId = objectData and objectData.id
|
|
}
|
|
end
|
|
|
|
activeGizmo:selectEntity(hitEntity)
|
|
|
|
if self.mode == "gizmo" and self.freeCamera and not self.cameraFocus then
|
|
self.cameraFocus = true
|
|
end
|
|
|
|
self:instructional()
|
|
end
|
|
|
|
function decorate.deselectEntity(self)
|
|
gizmo:deselectEntity()
|
|
self:instructional()
|
|
end
|
|
|
|
function decorate.getCamCoords(self)
|
|
return GetFinalRenderedCamCoord()
|
|
end
|
|
|
|
function decorate.getCamRot(self)
|
|
return GetFinalRenderedCamRot(2)
|
|
end
|
|
|
|
function decorate.toggleHideDecorate(self)
|
|
self.hide = not self.hide
|
|
SendReactMessage("toggle_hide_decorate", self.hide)
|
|
|
|
if self.hide then
|
|
self:setFocus(true, true)
|
|
else
|
|
self:setFocus(true, false)
|
|
end
|
|
end
|
|
|
|
function decorate.setFocus(self, focusState, keepInputState)
|
|
if focusState == nil then
|
|
self.focus = not self.focus
|
|
else
|
|
self.focus = focusState
|
|
end
|
|
|
|
SetNuiFocus(self.focus, self.focus)
|
|
|
|
if keepInputState ~= nil then
|
|
self.keepInput = keepInputState
|
|
else
|
|
self.keepInput = not self.focus
|
|
end
|
|
|
|
self.keepInput = keepInputState
|
|
SetNuiFocusKeepInput(self.keepInput)
|
|
|
|
if not self.keepInput and self.mode == "mgizmo" then
|
|
self:toggleGizmoMode("gizmo")
|
|
Debug("setFocus ::: toggleGizmoMode to gizmo because keepInput is false")
|
|
end
|
|
end
|
|
|
|
function decorate.placeObjectOnGround(self)
|
|
local currentHandle = self.currentObject and self.currentObject.handle
|
|
if not currentHandle then return end
|
|
|
|
PlaceObjectOnGroundProperly(currentHandle)
|
|
gizmo:updateGizmoEntity()
|
|
end
|
|
|
|
function decorate.toggleGizmoMode(self, targetMode)
|
|
if self.mode == "gizmo" and not self.keepInput then
|
|
return Debug("toggleGizmoMode ::: mgizmo mode is enabled and keepInput is true, so we do not toggle mode")
|
|
end
|
|
|
|
if targetMode then
|
|
if targetMode == self.mode then
|
|
return Debug("toggleGizmoMode ::: mode is already same", "mode", targetMode)
|
|
end
|
|
self.mode = targetMode
|
|
else
|
|
self.mode = (self.mode == "gizmo") and "mgizmo" or "gizmo"
|
|
end
|
|
|
|
SendReactMessage("toggle_gizmo_mode", self.mode)
|
|
Notification(i18n.t("decorate.gizmo_mode_toggled", { mode = self.mode }), "info")
|
|
|
|
gizmo:deselectEntity()
|
|
mgizmo:deselectEntity()
|
|
|
|
if self.mode == "gizmo" then
|
|
gizmo:handleCameraUpdate()
|
|
else
|
|
mgizmo:loop()
|
|
end
|
|
end
|
|
|
|
LIGHT_ITEMS = Config.Furniture.light.items
|
|
|
|
local dynamicFurnituresBackup = table.deepclone(Config.DynamicFurnitures)
|
|
|
|
function InitializeFurnitures()
|
|
Config.DynamicFurnitures = table.deepclone(dynamicFurnituresBackup)
|
|
Config.DoorModels = {}
|
|
|
|
for categoryKey, category in pairs(Config.Furniture) do
|
|
if categoryKey ~= "navigation" then
|
|
for _, item in pairs(category.items) do
|
|
if item.type then
|
|
Config.DynamicFurnitures[item.object] = item
|
|
end
|
|
if item.isDoor then
|
|
Config.DoorModels[item.object] = item
|
|
end
|
|
if item.colors then
|
|
for _, colorVariant in pairs(item.colors) do
|
|
if colorVariant.type then
|
|
Config.DynamicFurnitures[colorVariant.object] = colorVariant
|
|
end
|
|
if item.isDoor then
|
|
Config.DoorModels[colorVariant.object] = colorVariant
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
CreateThread(InitializeFurnitures)
|
|
|
|
function decorate.handleControls(self)
|
|
local lastOutlinedHandle = 0
|
|
|
|
CreateThread(function()
|
|
while self.active do
|
|
disablePlayerFiring(cache.playerId, true)
|
|
|
|
local currentHandle = self.currentObject and self.currentObject.handle
|
|
|
|
if lastOutlinedHandle ~= currentHandle then
|
|
SetEntityDrawOutline(lastOutlinedHandle, false)
|
|
SetEntityDrawOutline(currentHandle, true)
|
|
SetEntityDrawOutlineColor(0, 180, 255, 255)
|
|
end
|
|
|
|
lastOutlinedHandle = currentHandle
|
|
|
|
if isControlJustPressed(0, Keys.F5) or isDisabledControlJustPressed(0, Keys.F5) then
|
|
self:setFocus(true)
|
|
end
|
|
|
|
if isControlJustPressed(0, Keys.F3) or isDisabledControlJustPressed(0, Keys.F3) then
|
|
self:toggleFreeCamera()
|
|
end
|
|
|
|
if isControlJustPressed(0, Keys.Enter) or isDisabledControlJustPressed(0, Keys.Enter) then
|
|
if not (self.currentObject and self.currentObject.stashId) then
|
|
self:openBuyObjectModal()
|
|
end
|
|
end
|
|
|
|
if currentHandle then
|
|
if isControlJustPressed(0, Keys.Delete) or isDisabledControlJustPressed(0, Keys.Delete) then
|
|
self:removeCurrentObject()
|
|
end
|
|
|
|
if isControlJustPressed(0, Keys.G) or isDisabledControlJustPressed(0, Keys.G) then
|
|
self:placeObjectOnGround()
|
|
end
|
|
end
|
|
|
|
Wait(0)
|
|
end
|
|
end)
|
|
end
|
|
|
|
RegisterNetEvent("garages:decorate:open")
|
|
AddEventHandler("garages:decorate:open", function()
|
|
if not currentlyInGarage then
|
|
return Notification(i18n.t("decorate.not_in_garage"), "error")
|
|
end
|
|
|
|
local garageData = Config.Garages[currentlyInGarage]
|
|
if not garageData then
|
|
return Notification(i18n.t("decorate.invalid_garage"), "error")
|
|
end
|
|
|
|
if garageData.available or garageData.isImpound then
|
|
return Notification(i18n.t("decorate.not_support_decoration"), "error")
|
|
end
|
|
|
|
local playerIdentifier = GetPlayerIdentifier()
|
|
local isOwner = garageData.owner == playerIdentifier
|
|
|
|
if Config.DecorateOnlyAccessForOwner and not isOwner then
|
|
return Notification(i18n.t("decorate.not_owner"), "error")
|
|
elseif not HasKey(currentlyInGarage) and not isOwner then
|
|
return Notification(i18n.t("decorate.not_key_holder"), "error")
|
|
end
|
|
|
|
decorate:open()
|
|
end)
|
|
|
|
function decorate.toggleFreeCamera(self, forceState)
|
|
if forceState ~= nil then
|
|
self.freeCamera = forceState
|
|
else
|
|
self.freeCamera = not self.freeCamera
|
|
end
|
|
|
|
if not self.freeCamera then return end
|
|
|
|
SetPlayerControl(cache.playerId, false, 0)
|
|
|
|
local _, _, pedForward, pedPos = GetEntityMatrix(cache.ped)
|
|
local cameraStartPos = pedPos + pedForward * 2
|
|
local pedRotation = GetEntityRotation(cache.ped)
|
|
|
|
local freeCam = Utils.CreateCamera("DEFAULT_SCRIPTED_CAMERA", cameraStartPos, pedRotation, true)
|
|
|
|
self:instructional({
|
|
{ key = "focus_free_camera", label = "Focus Object" }
|
|
})
|
|
self.cameraFocus = false
|
|
|
|
CreateThread(function()
|
|
local camPos = cameraStartPos
|
|
local camRot = pedRotation
|
|
|
|
while self.active and self.freeCamera do
|
|
local newPos, newRot = Utils.HandleFlyCam(freeCam, { mouse = not self.cameraFocus })
|
|
camRot = newRot
|
|
camPos = newPos
|
|
|
|
DisableAllControlActions(0)
|
|
|
|
if isDisabledControlJustPressed(0, Keys.F) then
|
|
if self.mode == "gizmo" then
|
|
self.cameraFocus = not self.cameraFocus
|
|
else
|
|
Notification(i18n.t("decorate.focus_object_not_supported"), "error")
|
|
end
|
|
end
|
|
|
|
if self.cameraFocus and self.mode == "mgizmo" then
|
|
self.cameraFocus = false
|
|
end
|
|
|
|
if self.cameraFocus then
|
|
local currentObjHandle = self.currentObject and self.currentObject.handle
|
|
if currentObjHandle and DoesEntityExist(currentObjHandle) then
|
|
local objCoords = GetEntityCoords(currentObjHandle)
|
|
local objModel = GetEntityModel(currentObjHandle)
|
|
local minDim, maxDim = GetModelDimensions(objModel)
|
|
local objSize = #(maxDim - minDim)
|
|
local idealDist = math.max(objSize * 2.0, 3.0)
|
|
local objHeight = maxDim.z - minDim.z
|
|
|
|
local camToObj = camPos - objCoords
|
|
local dist = #camToObj
|
|
|
|
local pitch = math.deg(math.asin(camToObj.z / dist))
|
|
local yaw = math.deg(math.atan(-camToObj.x, camToObj.y))
|
|
local targetRot = vec3(pitch, 0.0, yaw)
|
|
|
|
local lerpedRot = vec3(
|
|
camRot.x + (targetRot.x - camRot.x) * CAMERA_LERP_SPEED,
|
|
camRot.y + (targetRot.y - camRot.y) * CAMERA_LERP_SPEED,
|
|
camRot.z + (targetRot.z - camRot.z) * CAMERA_LERP_SPEED
|
|
)
|
|
SetCamRot(freeCam, lerpedRot.x, lerpedRot.y, lerpedRot.z, 2)
|
|
|
|
if dist > idealDist * 1.5 or dist < idealDist * 0.5 then
|
|
local dirToObj = camToObj / dist
|
|
local targetPos = objCoords - dirToObj * idealDist
|
|
local lerpedPos = vec3(
|
|
camPos.x + (targetPos.x - camPos.x) * CAMERA_LERP_SPEED * 0.5,
|
|
camPos.y + (targetPos.y - camPos.y) * CAMERA_LERP_SPEED * 0.5,
|
|
camPos.z + (targetPos.z - camPos.z) * CAMERA_LERP_SPEED * 0.5
|
|
)
|
|
SetCamCoord(freeCam, lerpedPos.x, lerpedPos.y, lerpedPos.z)
|
|
camPos = lerpedPos
|
|
end
|
|
|
|
if dist < idealDist * 0.7 then
|
|
local orbitAngle = GetGameTimer() / 1000.0 * 0.3
|
|
local orbitRadius = objSize * 0.8
|
|
local targetOrbit = vec3(
|
|
objCoords.x + math.cos(orbitAngle) * orbitRadius,
|
|
objCoords.y + math.sin(orbitAngle) * orbitRadius,
|
|
objCoords.z + objHeight
|
|
)
|
|
local lerpedOrbit = vec3(
|
|
camPos.x + (targetOrbit.x - camPos.x) * 0.05,
|
|
camPos.y + (targetOrbit.y - camPos.y) * 0.05,
|
|
camPos.z + (targetOrbit.z - camPos.z) * 0.05
|
|
)
|
|
SetCamCoord(freeCam, lerpedOrbit.x, lerpedOrbit.y, lerpedOrbit.z)
|
|
end
|
|
end
|
|
end
|
|
|
|
Wait(0)
|
|
end
|
|
|
|
Utils.DestroyFlyCam(freeCam, 1000)
|
|
SetPlayerControl(cache.playerId, true, 0)
|
|
self:instructional()
|
|
end)
|
|
end
|
|
|
|
function decorate.removeCurrentObject(self)
|
|
local currentObj = self.currentObject
|
|
if not currentObj then return end
|
|
|
|
if currentObj.handle and not currentObj.stashId then
|
|
DeleteObject(currentObj.handle)
|
|
end
|
|
|
|
gizmo:deselectEntity()
|
|
self.currentObject = nil
|
|
SendReactMessage("remove_current_object")
|
|
Debug("Removed current object", self.currentObject)
|
|
end
|
|
|
|
exports("inDecorate", function()
|
|
return decorate.active
|
|
end)
|
|
|
|
function decorate.getObjectData(self, entityHandle)
|
|
local currentHandle = self.currentObject and self.currentObject.handle
|
|
if currentHandle == entityHandle then
|
|
return self.currentObject
|
|
end
|
|
|
|
for _, objData in pairs(decorate.objects) do
|
|
if objData.handle and DoesEntityExist(objData.handle) and objData.handle == entityHandle then
|
|
return objData
|
|
end
|
|
end
|
|
|
|
return false
|
|
end
|
|
|
|
function decorate.saveCurrentObject(self)
|
|
Debug("saveCurrentObject", "Current object", decorate.currentObject)
|
|
|
|
if not self.currentObject then return end
|
|
|
|
local objToSave = {
|
|
modelName = self.currentObject.modelName,
|
|
coords = GetEntityCoords(self.currentObject.handle),
|
|
rotation = GetEntityRotation(self.currentObject.handle),
|
|
handle = self.currentObject.handle,
|
|
inStash = false,
|
|
inside = currentlyInGarage ~= nil,
|
|
insideId = currentlyInGarage
|
|
}
|
|
|
|
self:removeCurrentObject()
|
|
|
|
return lib.callback.await("garages:decorate:saveObject", false, currentlyInGarage, objToSave)
|
|
end
|
|
|
|
function decorate.destroyObjects(self)
|
|
local objectsCopy = table.deepclone(decorate.objects)
|
|
decorate.objects = {}
|
|
|
|
for _, objData in pairs(objectsCopy) do
|
|
RemoveSpawnedObject(objData)
|
|
end
|
|
|
|
Debug("Unloaded objects")
|
|
end
|
|
|
|
function decorate.refreshObjects(self)
|
|
for _, objData in pairs(decorate.objects) do
|
|
RemoveSpawnedObject(objData)
|
|
end
|
|
end
|
|
|
|
function decorate.saveObjects(self)
|
|
for _, objData in pairs(decorate.objects) do
|
|
if objData.spawned and DoesEntityExist(objData.handle) then
|
|
local newCoords = GetEntityCoords(objData.handle)
|
|
local newRotation = GetEntityRotation(objData.handle)
|
|
|
|
if newCoords.x ~= objData.coords.x or newRotation.x ~= objData.rotation.x then
|
|
objData.coords = newCoords
|
|
objData.rotation = newRotation
|
|
|
|
TriggerServerEvent("garages:decorate:updateObject", currentlyInGarage, objData.id, {
|
|
coords = json.encode(newCoords),
|
|
rotation = json.encode(newRotation)
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function decorate.openBuyObjectModal(self)
|
|
if not self.currentObject then return end
|
|
SendReactMessage("open_buy_object_modal")
|
|
end
|
|
|
|
RegisterNetEvent("garages:decorate:updateObject")
|
|
AddEventHandler("garages:decorate:updateObject", function(garageId, objectId, newData)
|
|
if currentlyInGarage ~= garageId then
|
|
return Debug("garages:decorate:updateObject ::: insideID is not same", "currentlyInGarage", currentlyInGarage, "insideID", garageId)
|
|
end
|
|
|
|
local foundObject = table.find(decorate.objects, function(obj) return obj.id == objectId end)
|
|
if not foundObject then
|
|
Error("garages:decorate:updateObject :: Object not found", "id", objectId)
|
|
return
|
|
end
|
|
|
|
for key, value in pairs(newData) do
|
|
if key == "coords" and foundObject.spawned then
|
|
Debug("garages:decorate:updateObject ::: SetEntityCoords", "object", foundObject.handle, "coords", value)
|
|
SetEntityCoords(foundObject.handle, value.x, value.y, value.z, false, false, false, false)
|
|
elseif key == "rotation" and foundObject.spawned then
|
|
Debug("garages:decorate:updateObject ::: SetEntityRotation", "object", foundObject.handle, "rotation", value)
|
|
SetEntityRotation(foundObject.handle, value.x, value.y, value.z, 0, false)
|
|
end
|
|
foundObject[key] = value
|
|
Debug("Updated object", "object", foundObject.id, "key", key, "value", value)
|
|
end
|
|
end)
|
|
|
|
function RemoveSpawnedObject(objData)
|
|
if not objData.spawned then return false end
|
|
DeleteObject(objData.handle)
|
|
objData.spawned = false
|
|
end
|
|
|
|
RegisterNetEvent("garages:decorate:sellFurniture")
|
|
AddEventHandler("garages:decorate:sellFurniture", function(garageId, objectId)
|
|
if currentlyInGarage ~= garageId then
|
|
return Debug("garages:decorate:sellFurniture ::: decorateId is not same", "CurrentGarage", currentlyInGarage, "garage", garageId)
|
|
end
|
|
|
|
local foundObject = table.find(decorate.objects, function(obj) return obj.id == objectId end)
|
|
if not foundObject then
|
|
Error("garages:decorate:sellFurniture ::: Object not found", "id", objectId)
|
|
return
|
|
end
|
|
|
|
RemoveSpawnedObject(foundObject)
|
|
decorate.objects = table.filter(decorate.objects, function(obj) return obj.id ~= objectId end)
|
|
Debug("garages:decorate:sellFurniture", "object is deleted from cache", foundObject.id)
|
|
end)
|
|
|
|
RegisterNetEvent("garages:decorate:addObject")
|
|
AddEventHandler("garages:decorate:addObject", function(garageId, objectData)
|
|
if currentlyInGarage ~= garageId then
|
|
return Debug("garages:decorate:addObject ::: garage is not same", "Entered Garage", currentlyInGarage, "garage", garageId)
|
|
end
|
|
|
|
decorate.objects[#decorate.objects + 1] = objectData
|
|
Debug("Added object to data", "data", objectData)
|
|
end)
|
|
|
|
function SpawnObject(modelName, coords, rotation)
|
|
local modelHash = joaat(modelName)
|
|
lib.requestModel(modelHash)
|
|
|
|
local entity = CreateObject(modelHash, coords.x, coords.y, coords.z, false, false, false)
|
|
SetEntityAlpha(entity, 0, false)
|
|
|
|
CreateThread(function()
|
|
for alpha = 0, 255, 51 do
|
|
Wait(50)
|
|
SetEntityAlpha(entity, alpha, false)
|
|
end
|
|
end)
|
|
|
|
if rotation then
|
|
SetEntityRotation(entity, rotation.x, rotation.y, rotation.z, 0, false)
|
|
end
|
|
|
|
SetEntityCompletelyDisableCollision(entity, true, false)
|
|
|
|
if not (Config.DynamicDoors and Config.DoorModels[modelName]) then
|
|
FreezeEntityPosition(entity, true)
|
|
end
|
|
|
|
SetModelAsNoLongerNeeded(modelHash)
|
|
Wait(0)
|
|
SetEntityCoords(entity, coords.x, coords.y, coords.z, false, false, false, false)
|
|
return entity
|
|
end
|
|
|
|
function decorate.canAccessStash(self, stashUniq)
|
|
if not currentlyInGarage then return false end
|
|
return HasKey(currentlyInGarage)
|
|
end
|
|
|
|
function decorate.doorAnim(self)
|
|
lib.playAnim(cache.ped, "anim@heists@keycard@", "exit")
|
|
Wait(400)
|
|
ClearPedTasks(cache.ped)
|
|
end
|
|
|
|
local TEXT_DYNAMIC = i18n.t("drawtext.dynamic")
|
|
local TEXT_STASH = i18n.t("drawtext.stash")
|
|
local TEXT_GARDROBE = i18n.t("drawtext.gardrobe")
|
|
local TEXT_DELETE = i18n.t("drawtext.delete_illegal")
|
|
|
|
function decorate.checkNearObjects(self)
|
|
CreateThread(function()
|
|
while self.objects and #self.objects > 0 do
|
|
local waitTime = 500
|
|
local pedCoords = GetEntityCoords(cache.ped)
|
|
|
|
for _, objData in pairs(self.objects) do
|
|
local objCoords = vec3(objData.coords.x, objData.coords.y, objData.coords.z)
|
|
local distance = #(objCoords - pedCoords)
|
|
|
|
if distance <= 1.5 then
|
|
local dynamicConfig = Config.DynamicFurnitures[objData.modelName]
|
|
if dynamicConfig then
|
|
local interactPos = GetOffsetFromEntityInWorldCoords(
|
|
objData.handle,
|
|
dynamicConfig.offset.x, dynamicConfig.offset.y, dynamicConfig.offset.z
|
|
)
|
|
|
|
if dynamicConfig.event then
|
|
waitTime = 0
|
|
DrawText3D(interactPos.x, interactPos.y, interactPos.z, TEXT_DYNAMIC, "Store", "interact", "E")
|
|
if isControlJustPressed(0, Keys.E) then
|
|
TriggerEvent(dynamicConfig.event, objData.uniq)
|
|
end
|
|
elseif dynamicConfig.type == "stash" then
|
|
waitTime = 0
|
|
DrawText3D(interactPos.x, interactPos.y, interactPos.z, TEXT_STASH, "stash_access", "E")
|
|
if isControlJustPressed(0, Keys.E) then
|
|
if self:canAccessStash(objData.uniq) then
|
|
openStash(dynamicConfig.stash, objData.uniq)
|
|
end
|
|
end
|
|
elseif dynamicConfig.type == "gardrobe" then
|
|
waitTime = 0
|
|
DrawText3D(interactPos.x, interactPos.y, interactPos.z, TEXT_GARDROBE, "open_wardrobe", "E")
|
|
if isControlJustPressed(0, Keys.E) then
|
|
openWardrobe()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Wait(waitTime)
|
|
end
|
|
end)
|
|
end
|
|
|
|
function decorate.getObjects(self, garageId)
|
|
self:destroyObjects()
|
|
Debug("decorate:getObjects", "id", garageId)
|
|
|
|
local objects = lib.callback.await("garages:decorate:getDecorations", 0, garageId)
|
|
self.objects = objects
|
|
|
|
self:checkNearObjects()
|
|
Debug("Loaded objects", "data", self.objects)
|
|
end
|
|
|
|
local lightObjectModels = {}
|
|
local lightObjectsCache = {}
|
|
|
|
CreateThread(function()
|
|
for _, item in pairs(LIGHT_ITEMS) do
|
|
table.insert(lightObjectModels, item.object)
|
|
end
|
|
|
|
while true do
|
|
if not decorate.objects then
|
|
lightObjectsCache = {}
|
|
else
|
|
local lightObjects = table.deepclone(table.filter(decorate.objects, function(obj)
|
|
return table.includes(lightObjectModels, obj.modelName)
|
|
end))
|
|
lightObjectsCache = lightObjects
|
|
|
|
for idx, objData in pairs(lightObjectsCache) do
|
|
if objData.handle and DoesEntityExist(objData.handle) then
|
|
if not objData.inside or currentlyInGarage then
|
|
local rotation = GetEntityRotation(objData.handle)
|
|
local coords = GetEntityCoords(objData.handle)
|
|
local direction = RotationToDirection(rotation)
|
|
lightObjectsCache[idx].position = coords
|
|
lightObjectsCache[idx].direction = direction
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Wait(500)
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
local waitTime = 1250
|
|
|
|
for _, objData in pairs(lightObjectsCache) do
|
|
if objData.handle and DoesEntityExist(objData.handle) then
|
|
local lightData = objData.lightData
|
|
if not lightData or not lightData.active then goto continue end
|
|
|
|
if objData.position then
|
|
waitTime = 0
|
|
local pos = objData.position
|
|
local dir = objData.direction
|
|
local rgb = (lightData and lightData.rgb) or { r = 255, g = 255, b = 255 }
|
|
local intensity = ((lightData and lightData.intensity) or Config.DefaultLightIntensity) + 0.0
|
|
|
|
DrawSpotLight(pos.x, pos.y, pos.z, dir.x, dir.y, dir.z,
|
|
rgb.r, rgb.g, rgb.b, 100.0, 20.0, 1.0, intensity, 0.0)
|
|
end
|
|
end
|
|
::continue::
|
|
end
|
|
|
|
Wait(waitTime)
|
|
end
|
|
end)
|
|
|
|
CreateThread(function()
|
|
while true do
|
|
local waitTime = decorate.active and 300 or 1250
|
|
local pedCoords = GetEntityCoords(cache.ped)
|
|
|
|
if not decorate.objects then
|
|
Wait(waitTime)
|
|
else
|
|
for _, objData in pairs(decorate.objects) do
|
|
if objData.inStash then
|
|
if objData.spawned then
|
|
DeleteObject(objData.handle)
|
|
objData.spawned = false
|
|
Debug("Deleted object because its setted to inStash", "object", objData.handle)
|
|
end
|
|
else
|
|
if not objData.coords then
|
|
Error("Object coords is nil we skipping it.", "object", objData)
|
|
else
|
|
objData.coords = vec3(objData.coords.x, objData.coords.y, objData.coords.z)
|
|
|
|
if objData.coords.x == 0.0 and objData.coords.y == 0.0 and objData.coords.z == 0.0 then
|
|
if EditorCamera then
|
|
local camData = Utils.GetCamera()
|
|
local camForward = Utils.GetForwardVector(camData.rotation) * 5.0
|
|
local camPos = camData.coords + camForward
|
|
|
|
Debug("Load Decorations : Object is from ikea. We setted it to camera center", "v", objData)
|
|
|
|
objData.coords = vec3(camPos.x, camPos.y, camPos.z)
|
|
decorate:saveObjects()
|
|
end
|
|
end
|
|
|
|
local distance = #(pedCoords - objData.coords)
|
|
|
|
if distance <= Config.SpawnDistance then
|
|
if not objData.spawned then
|
|
local entityHandle = SpawnObject(objData.modelName, objData.coords, objData.rotation)
|
|
if entityHandle then
|
|
objData.handle = entityHandle
|
|
|
|
local stashId = decorate.currentObject and decorate.currentObject.stashId
|
|
if stashId == objData.id then
|
|
decorate.currentObject.handle = entityHandle
|
|
decorate:selectEntity(entityHandle)
|
|
end
|
|
else
|
|
objData.handle = 0
|
|
Warning("This model is not loaded. Please check if the model is valid. if its not delete it from the list", objData.modelName)
|
|
end
|
|
objData.spawned = true
|
|
end
|
|
elseif distance > Config.SpawnDistance then
|
|
if objData.spawned then
|
|
RemoveSpawnedObject(objData)
|
|
Debug("Deleted object", "object", objData.handle)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
Wait(waitTime)
|
|
end
|
|
end
|
|
end)
|
|
|
|
local function AddFurnitureToCategory(categoryName, itemData)
|
|
local category = Config.Furniture[categoryName]
|
|
if category then
|
|
table.insert(category.items, itemData)
|
|
InitializeFurnitures()
|
|
else
|
|
print("Error: Category " .. categoryName .. " does not exist in furniture settings")
|
|
end
|
|
end
|
|
|
|
exports("AddFurniture", function(categoryName, itemData)
|
|
return AddFurnitureToCategory(categoryName, itemData)
|
|
end)
|
|
|
|
exports("AddShell", function(shellData)
|
|
Config.Shells[#Config.Shells + 1] = shellData
|
|
Debug("Added shell", shellData)
|
|
end)
|
|
|
|
AddEventHandler("onResourceStop", function(resourceName)
|
|
if GetCurrentResourceName() ~= resourceName then return end
|
|
decorate:destroyObjects()
|
|
decorate:close()
|
|
end)
|
|
|
|
RegisterCommand("garagedecorate", function()
|
|
TriggerEvent("garages:decorate:open")
|
|
end)
|