local pendingScreenshots = {} local uploadedResults = {} furnitureCreator = { visible = false, token = nil } function furnitureCreator.updateUI(self) if not self.visible then return end local furnitureCopy = table.deepclone(Config.Furniture) for _, category in pairs(furnitureCopy) do category.items = table.filter(category.items, function(item) return item.creator end) end SendReactMessage("toggle_furniture_creator", { visible = true, furniture = furnitureCopy }) end function furnitureCreator.open(self) ToggleHud(false) self.visible = true SetNuiFocus(true, true) self:updateUI() Debug("Furniture Creator opened") end function furnitureCreator.close(self) if not self.visible then return end self.visible = false SendReactMessage("toggle_furniture_creator", { visible = false }) ToggleHud(true) SetNuiFocus(false, false) Debug("Furniture Creator closed") end RegisterNetEvent("garages:syncFurnitureData") AddEventHandler("garages:syncFurnitureData", function(furnitureData) Config.Furniture = furnitureData InitializeFurnitures() end) RegisterCommand(Config.FurniCreatorCommand, function() local hasPermission = lib.callback.await("garages:hasPermission", 0) if not hasPermission then return Notification(i18n.t("no_permission"), "error") end furnitureCreator:open() end) function furnitureCreator.takeScreenshot(self, requestId) Wait(Config.FurniCreator.interval) if not self.token then self.token = lib.callback.await("garages:getFiveManageToken", false) end if not self.token then return Notification(i18n.t("furniture_creator.token_not_set"), "error") end pendingScreenshots[requestId] = { name = requestId, timestamp = GetGameTimer() } Notification(i18n.t("furniture_creator.uploading_image"), "info") exports["screenshot-basic"]:requestScreenshot({ encoding = "png" }, function(imageData) SendReactMessage("upload_image", { image = imageData, fiveManageToken = self.token, requestId = requestId }) end) Debug("Screenshot Request Sent", requestId) return requestId end function furnitureCreator.hasPendingScreenshots(self) return next(pendingScreenshots) ~= nil end RegisterNUICallback("handle_uploaded_image", function(data, cb) local requestId = data.requestId local pending = pendingScreenshots[requestId] if not pending then Error("No data found for requestId", data.requestId) return cb("error") end uploadedResults[requestId] = { filePath = data.url, name = pending.name } Debug("Screenshot Results", uploadedResults[requestId], "data", data) pendingScreenshots[requestId] = nil Notification(i18n.t("furniture_creator.image_uploaded"), "success") cb("ok") end) local GREEN_BOX_ORIGIN = vec3(-2407.29736328125, -2023.800537109375, 690.6831665039062) local GREEN_BOX_OFFSET = vec3(0.0, 0.0, 0.5) local MIN_CAMERA_DIST = 3.0 local ROTATE_SPEED = 100.0 local VERTICAL_SPEED = 0.5 local ZOOM_SPEED = 15.0 local MAX_ZOOM = 10.0 function furnitureCreator.takeObjectScreenshot(self, objectModelName) if self.inGreenBox then return end self.inGreenBox = true self.initialPlayerCoords = GetEntityCoords(cache.ped) FreezeEntityPosition(cache.ped, true) SetEntityAlpha(cache.ped, 0, false) local greenBoxModel = joaat("qs_gradient_032") lib.requestModel(greenBoxModel) local greenBoxEntity = CreateObject(greenBoxModel, GREEN_BOX_ORIGIN.x, GREEN_BOX_ORIGIN.y, GREEN_BOX_ORIGIN.z, false, false, false) self.greenBox = greenBoxEntity FreezeEntityPosition(greenBoxEntity, true) local objectModel = joaat(objectModelName) lib.requestModel(objectModel) local spawnPos = GREEN_BOX_ORIGIN + GREEN_BOX_OFFSET local objectEntity = CreateObject(objectModel, spawnPos.x, spawnPos.y, spawnPos.z, false, false, false) self.object = objectEntity FreezeEntityPosition(objectEntity, true) SetEntityHeading(objectEntity, 0.0) Wait(100) local objectCoords = GetEntityCoords(objectEntity) local minDim, maxDim = GetModelDimensions(objectModel) local objectSize = #(maxDim - minDim) local cameraOffset = math.min(objectSize * 2.5, MIN_CAMERA_DIST) local verticalMidpoint = (maxDim.z - minDim.z) / 2 local cameraPos = vec3(objectCoords.x + cameraOffset, objectCoords.y, objectCoords.z + verticalMidpoint) local greenBoxCamera = Utils.CreateCamera("DEFAULT_SCRIPTED_CAMERA", cameraPos, vec3(0, 0, 0), true, nil, 0) self.greenBoxCamera = greenBoxCamera SetCamCoord(greenBoxCamera, cameraPos.x, cameraPos.y, cameraPos.z) PointCamAtCoord(greenBoxCamera, objectCoords.x, objectCoords.y, objectCoords.z) Utils.DrawInstructional({ "zoom", "done", "cancel", "up", "rotate_z" }) local heading = 0.0 local verticalOffset = verticalMidpoint local currentZoom = cameraOffset local confirmed = false while self.inGreenBox do DisableAllControlActions(0) SetEntityHeading(objectEntity, heading) SetEntityCoords(objectEntity, objectCoords.x, objectCoords.y, objectCoords.z + verticalOffset, false, false, false, false) if IsDisabledControlPressed(0, ActionControls.rotate_z.codes[1]) then heading = (heading + ROTATE_SPEED * GetFrameTime()) % 360 elseif IsDisabledControlPressed(0, ActionControls.rotate_z.codes[2]) then heading = (heading - ROTATE_SPEED * GetFrameTime()) % 360 end if IsDisabledControlPressed(0, ActionControls.up.codes[1]) then verticalOffset = verticalOffset + VERTICAL_SPEED * GetFrameTime() elseif IsDisabledControlPressed(0, ActionControls.up.codes[2]) then verticalOffset = verticalOffset - VERTICAL_SPEED * GetFrameTime() end if IsDisabledControlPressed(0, ActionControls.rotate_z_scroll.codes[1]) then currentZoom = math.max(MIN_CAMERA_DIST / 3, currentZoom - ZOOM_SPEED * GetFrameTime()) local newCamPos = vec3(objectCoords.x + currentZoom, objectCoords.y, objectCoords.z + verticalMidpoint) cameraPos = newCamPos SetCamCoord(greenBoxCamera, newCamPos.x, newCamPos.y, newCamPos.z) elseif IsDisabledControlPressed(0, ActionControls.rotate_z_scroll.codes[2]) then currentZoom = math.min(MAX_ZOOM, currentZoom + ZOOM_SPEED * GetFrameTime()) local newCamPos = vec3(objectCoords.x + currentZoom, objectCoords.y, objectCoords.z + verticalMidpoint) cameraPos = newCamPos SetCamCoord(greenBoxCamera, newCamPos.x, newCamPos.y, newCamPos.z) end if IsDisabledControlJustPressed(0, ActionControls.done.codes[1]) then confirmed = true self.inGreenBox = false break end if IsDisabledControlJustPressed(0, ActionControls.cancel.codes[1]) then self.inGreenBox = false Notification(i18n.t("furniture_creator.screenshot_cancelled"), "error") break end Wait(0) end Utils.RemoveInstructional() if confirmed then local screenshotId = self:takeScreenshot(objectModelName) if not screenshotId then return end while self:hasPendingScreenshots() do Wait(0) end local resultData = uploadedResults[screenshotId] self:destroyScreenshot() uploadedResults[screenshotId] = nil return resultData and resultData.filePath else self:destroyScreenshot() return nil end end RegisterCommand("test", function() local url = furnitureCreator:takeObjectScreenshot("prop_rub_washer_01") Debug("Screenshot URL", url) end) function furnitureCreator.destroyScreenshot(self) if self.greenBox and DoesEntityExist(self.greenBox) then DeleteEntity(self.greenBox) self.greenBox = nil end if self.object and DoesEntityExist(self.object) then DeleteEntity(self.object) self.object = nil end if self.greenBoxCamera then Utils.DestroyFlyCam(self.greenBoxCamera) self.greenBoxCamera = nil end if self.initialPlayerCoords then local coords = self.initialPlayerCoords SetEntityCoords(cache.ped, coords.x, coords.y, coords.z, false, false, false, false) FreezeEntityPosition(cache.ped, false) SetEntityAlpha(cache.ped, 255, false) self.initialPlayerCoords = nil end self.inGreenBox = false end AddEventHandler("onResourceStop", function(resourceName) if resourceName == GetCurrentResourceName() then furnitureCreator:destroyScreenshot() end end)