Scaleform = Scaleform or Require("lib/scaleform/client/scaleform.lua") Utility = Utility or Require("lib/utility/client/utility.lua") Raycast = Raycast or Require("lib/raycast/client/raycast.lua") Language = Language or Require("modules/locales/shared.lua") PlaceableObject = PlaceableObject or {} -- Register key mappings for placement controls RegisterKeyMapping('+place_object', locale('placeable_object.object_place'), 'mouse_button', 'MOUSE_LEFT') RegisterKeyMapping('+cancel_placement', locale('placeable_object.object_cancel'), 'mouse_button', 'MOUSE_RIGHT') RegisterKeyMapping('+rotate_left', locale('placeable_object.rotate_left'), 'keyboard', 'LEFT') RegisterKeyMapping('+rotate_right', locale('placeable_object.rotate_right'), 'keyboard', 'RIGHT') RegisterKeyMapping('+scroll_up', locale('placeable_object.object_scroll_up'), 'mouse_wheel', 'IOM_WHEEL_UP') RegisterKeyMapping('+scroll_down', locale('placeable_object.object_scroll_down'), 'mouse_wheel', 'IOM_WHEEL_DOWN') RegisterKeyMapping('+depth_modifier', locale('placeable_object.depth_modifier'), 'keyboard', 'LCONTROL') local state = { isPlacing = false, currentEntity = nil, mode = 'normal', -- 'normal' or 'movement' promise = nil, scaleform = nil, -- Placement settings depth = 2.0, heading = 0.0, height = 0.0, snapToGround = true, paused = false, -- Current settings settings = {}, boundaryCheck = nil, -- Key press states keys = { placeObject = false, cancelPlacement = false, rotateLeft = false, rotateRight = false, scrollUp = false, scrollDown = false, depthModifier = false } } -- Command handlers for key mappings RegisterCommand('+place_object', function() if state.isPlacing then state.keys.placeObject = true end end, false) RegisterCommand('-place_object', function() state.keys.placeObject = false end, false) RegisterCommand('+cancel_placement', function() if state.isPlacing then state.keys.cancelPlacement = true end end, false) RegisterCommand('-cancel_placement', function() state.keys.cancelPlacement = false end, false) RegisterCommand('+rotate_left', function() if state.isPlacing then state.keys.rotateLeft = true end end, false) RegisterCommand('-rotate_left', function() state.keys.rotateLeft = false end, false) RegisterCommand('+rotate_right', function() if state.isPlacing then state.keys.rotateRight = true end end, false) RegisterCommand('-rotate_right', function() state.keys.rotateRight = false end, false) RegisterCommand('+scroll_up', function() if state.isPlacing then state.keys.scrollUp = true end end, false) RegisterCommand('-scroll_up', function() state.keys.scrollUp = false end, false) RegisterCommand('+scroll_down', function() if state.isPlacing then state.keys.scrollDown = true end end, false) RegisterCommand('-scroll_down', function() state.keys.scrollDown = false end, false) RegisterCommand('+depth_modifier', function() if state.isPlacing then state.keys.depthModifier = true end end, false) RegisterCommand('-depth_modifier', function() state.keys.depthModifier = false end, false) -- Utility functions local function getMouseWorldPos(depth) local screenX = GetDisabledControlNormal(0, 239) local screenY = GetDisabledControlNormal(0, 240) local world, normal = GetWorldCoordFromScreenCoord(screenX, screenY) local playerPos = GetEntityCoords(PlayerPedId()) return playerPos + normal * depth end -- local function isInBoundary(pos, boundary) -- if not boundary then return true end -- local x, y, z = table.unpack(pos) -- -- Handle legacy min/max boundary format for backwards compatibility -- if boundary.min and boundary.max then -- local minX, minY, minZ = table.unpack(boundary.min) -- local maxX, maxY, maxZ = table.unpack(boundary.max) -- return x >= minX and x <= maxX and y >= minY and y <= maxY and z >= minZ and z <= maxZ -- end -- -- Handle list of points (polygon boundary) -- if boundary.points and #boundary.points > 0 then -- local points = boundary.points -- local minZ = boundary.minZ or -math.huge -- local maxZ = boundary.maxZ or math.huge -- -- Check Z bounds first -- if z < minZ or z > maxZ then -- return false -- end -- -- Point-in-polygon test using ray casting algorithm (improved version) -- local inside = false -- local n = #points -- for i = 1, n do -- local j = i == n and 1 or i + 1 -- Next point (wrap around) -- local xi, yi = points[i].x or points[i][1], points[i].y or points[i][2] -- local xj, yj = points[j].x or points[j][1], points[j].y or points[j][2] -- -- Ensure xi, yi, xj, yj are numbers -- if not (xi and yi and xj and yj) then -- goto continue -- end -- -- Ray casting test -- if ((yi > y) ~= (yj > y)) then -- -- Calculate intersection point -- local intersect = (xj - xi) * (y - yi) / (yj - yi) + xi -- if x < intersect then -- inside = not inside -- end -- end -- ::continue:: -- end -- return inside -- end -- -- Fallback to true if boundary format is not recognized -- return true -- end local function checkMaterialAndBoundary() if not state.currentEntity then return true end local pos = GetEntityCoords(state.currentEntity) local inBounds = Bridge.Math.InBoundary(pos, state.settings.boundary) -- Check built-in boundary first if state.settings.boundary and not inBounds then return false end -- Check custom boundary function if provided if state.settings.customCheck then local customResult = state.settings.customCheck(pos, state.currentEntity, state.settings) if not customResult then return false end end -- Check allowed materials if state.settings.allowedMats then local hit, _, _, _, materialHash = GetShapeTestResult(StartShapeTestRay(pos.x, pos.y, pos.z + 1.0, pos.x, pos.y, pos.z - 5.0, -1, 0, 7)) if hit == 1 then for _, allowedMat in ipairs(state.settings.allowedMats) do if materialHash == GetHashKey(allowedMat) then return inBounds end end return false end end return inBounds end local function checkMaterialAndBoundaryDetailed() if not state.currentEntity then return true, true, true end local pos = GetEntityCoords(state.currentEntity) local inBounds = Bridge.Math.InBoundary(pos, state.settings.boundary) local customCheckPassed = true local materialCheckPassed = true -- Check built-in boundary first if state.settings.boundary and not inBounds then return false, false, customCheckPassed end -- Check custom boundary function if provided if state.settings.customCheck then customCheckPassed = state.settings.customCheck(pos, state.currentEntity, state.settings) if not customCheckPassed then return false, inBounds, false end end -- Check allowed materials if state.settings.allowedMats then local hit, _, _, _, materialHash = GetShapeTestResult(StartShapeTestRay(pos.x, pos.y, pos.z + 1.0, pos.x, pos.y, pos.z - 5.0, -1, 0, 7)) if hit == 1 then for _, allowedMat in ipairs(state.settings.allowedMats) do if materialHash == GetHashKey(allowedMat) then return inBounds, inBounds, customCheckPassed end end materialCheckPassed = false return false, inBounds, customCheckPassed end end return inBounds, inBounds, customCheckPassed end -- local function setupInstructionalButtons() -- local buttons = {} -- -- Common buttons -- table.insert(buttons, {type = "SET_DATA_SLOT", name = state.settings.config?.place_object?.name or 'Place Object:', keyIndex = state.settings.config?.place_object?.key or {223}, int = 5}) -- table.insert(buttons, {type = "SET_DATA_SLOT", name = state.settings.config?.cancel_placement?.name or 'Cancel:', keyIndex = state.settings.config?.cancel_placement?.key or {25}, int = 4}) -- if state.mode == 'normal' then -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Rotate:', keyIndex = {241, 242}, int = 3}) -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Depth:', keyIndex = {224}, int = 2}) -- if state.settings.allowVertical then -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Height:', keyIndex = {16, 17}, int = 1}) -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Toggle Ground Snap:', keyIndex = {19}, int = 0}) -- end -- if state.settings.allowMovement then -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Movement Mode:', keyIndex = {38}, int = 6}) -- end -- elseif state.mode == 'movement' then -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Move:', keyIndex = {32, 33, 34, 35}, int = 3}) -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Rotate:', keyIndex = {174, 175}, int = 2}) -- if state.settings.allowVertical then -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Up/Down:', keyIndex = {85, 48}, int = 1}) -- end -- if state.settings.allowNormal then -- table.insert(buttons, {type = "SET_DATA_SLOT", name = 'Normal Mode:', keyIndex = {38}, int = 0}) -- end -- end -- table.insert(buttons, {type = "DRAW_INSTRUCTIONAL_BUTTONS"}) -- table.insert(buttons, {type = "SET_BACKGROUND_COLOUR"}) -- -- return Scaleform.SetupInstructionalButtons(buttons) -- return nil -- Scaleform disabled for now -- end local function drawBoundaryBox(boundary) if not boundary then return end -- Handle legacy min/max boundary format for backwards compatibility if boundary.min and boundary.max then local min = boundary.min local max = boundary.max -- Define the 8 corners of the box local corners = { vector3(min.x, min.y, min.z), -- 1 vector3(max.x, min.y, min.z), -- 2 vector3(max.x, max.y, min.z), -- 3 vector3(min.x, max.y, min.z), -- 4 vector3(min.x, min.y, max.z), -- 5 vector3(max.x, min.y, max.z), -- 6 vector3(max.x, max.y, max.z), -- 7 vector3(min.x, max.y, max.z), -- 8 } -- Draw wireframe box local r, g, b, a = 0, 255, 0, 100 -- Bottom face DrawLine(corners[1].x, corners[1].y, corners[1].z, corners[2].x, corners[2].y, corners[2].z, r, g, b, a) DrawLine(corners[2].x, corners[2].y, corners[2].z, corners[3].x, corners[3].y, corners[3].z, r, g, b, a) DrawLine(corners[3].x, corners[3].y, corners[3].z, corners[4].x, corners[4].y, corners[4].z, r, g, b, a) DrawLine(corners[4].x, corners[4].y, corners[4].z, corners[1].x, corners[1].y, corners[1].z, r, g, b, a) -- Top face DrawLine(corners[5].x, corners[5].y, corners[5].z, corners[6].x, corners[6].y, corners[6].z, r, g, b, a) DrawLine(corners[6].x, corners[6].y, corners[6].z, corners[7].x, corners[7].y, corners[7].z, r, g, b, a) DrawLine(corners[7].x, corners[7].y, corners[7].z, corners[8].x, corners[8].y, corners[8].z, r, g, b, a) DrawLine(corners[8].x, corners[8].y, corners[8].z, corners[5].x, corners[5].y, corners[5].z, r, g, b, a) -- Vertical edges DrawLine(corners[1].x, corners[1].y, corners[1].z, corners[5].x, corners[5].y, corners[5].z, r, g, b, a) DrawLine(corners[2].x, corners[2].y, corners[2].z, corners[6].x, corners[6].y, corners[6].z, r, g, b, a) DrawLine(corners[3].x, corners[3].y, corners[3].z, corners[7].x, corners[7].y, corners[7].z, r, g, b, a) DrawLine(corners[4].x, corners[4].y, corners[4].z, corners[8].x, corners[8].y, corners[8].z, r, g, b, a) return end -- Handle list of points (polygon boundary) if boundary.points and #boundary.points > 0 then local points = boundary.points local minZ = boundary.minZ or 0 local maxZ = boundary.maxZ or 50 local r, g, b, a = 0, 255, 0, 100 -- Draw bottom polygon outline for i = 1, #points do local currentPoint = points[i] local nextPoint = points[i % #points + 1] -- Wrap around to first point local x1, y1 = currentPoint.x or currentPoint[1], currentPoint.y or currentPoint[2] local x2, y2 = nextPoint.x or nextPoint[1], nextPoint.y or nextPoint[2] -- Bottom edge DrawLine(x1, y1, minZ, x2, y2, minZ, r, g, b, a) -- Top edge DrawLine(x1, y1, maxZ, x2, y2, maxZ, r, g, b, a) -- Vertical edge DrawLine(x1, y1, minZ, x1, y1, maxZ, r, g, b, a) end return end end local function drawEntityBoundingBox(entity, inBounds) if not entity or not DoesEntityExist(entity) then return end -- Enable entity outline SetEntityDrawOutlineShader(1) SetEntityDrawOutline(entity, true) -- Set color based on boundary status if inBounds then -- Green outline for valid placement SetEntityDrawOutlineColor(0, 255, 0, 255) else -- Red outline for invalid placement SetEntityDrawOutlineColor(255, 0, 0, 255) end end local function handleNormalMode() if not state.isPlacing or state.mode ~= 'normal' or state.paused then return end -- Disable conflicting controls DisableControlAction(0, 24, true) -- Attack DisableControlAction(0, 25, true) -- Aim DisableControlAction(0, 36, true) -- Duck local moveSpeed = state.keys.depthModifier and (state.settings.depthStep or 0.1) or (state.settings.rotationStep or 0.5) -- Scroll wheel controls using key mappings if state.keys.depthModifier then -- Depth modifier held - depth control if state.keys.scrollUp then state.keys.scrollUp = false -- Reset the key state state.depth = math.min(state.settings.maxDepth, state.depth + moveSpeed) -- Use maxDepth setting elseif state.keys.scrollDown then state.keys.scrollDown = false -- Reset the key state state.depth = math.max(1.0, state.depth - moveSpeed) -- Fixed: scroll down decreases distance end else -- Regular scroll - rotation if state.keys.scrollUp then state.keys.scrollUp = false -- Reset the key state state.heading = state.heading - 5.0 -- Fixed: scroll up = counterclockwise elseif state.keys.scrollDown then state.keys.scrollDown = false -- Reset the key state state.heading = state.heading + 5.0 -- Fixed: scroll down = clockwise end end -- -- Arrow key rotation using key mappings -- if state.keys.rotateLeft then -- state.heading = state.heading + 2.0 -- elseif state.keys.rotateRight then -- state.heading = state.heading - 2.0 -- end -- Height controls (only if vertical movement allowed and not snapped to ground) if state.settings.allowVertical and not state.snapToGround then if IsControlPressed(0, 16) then -- Q state.height = state.height + (state.settings.heightStep or 0.5) elseif IsControlPressed(0, 17) then -- E state.height = state.height - (state.settings.heightStep or 0.5) end end -- Toggle ground snap if state.settings.allowVertical and IsControlJustPressed(0, 19) then -- Alt state.snapToGround = not state.snapToGround if state.snapToGround then state.height = 0.0 end end -- Switch to movement mode if state.settings.allowMovement and IsControlJustPressed(0, 38) then -- E state.mode = 'movement' SetEntityCollision(state.currentEntity, false, false) end -- Update entity position local pos = getMouseWorldPos(state.depth) if not state.snapToGround and state.settings.allowVertical then pos = pos + vector3(0, 0, state.height) end if state.currentEntity then SetEntityCoords(state.currentEntity, pos.x, pos.y, pos.z, false, false, false, false) SetEntityHeading(state.currentEntity, state.heading) if state.snapToGround then local slerp = PlaceObjectOnGroundProperly(state.currentEntity) if not slerp then -- If the object can't be placed on the ground, adjust its Z position local groundZ, _z = GetGroundZFor_3dCoord(pos.x, pos.y, pos.z + 50, false) if groundZ then SetEntityCoords(state.currentEntity, pos.x, pos.y, _z, false, false, false, true) end end end end -- Visual feedback if not state.settings.disableSphere then DrawSphere(pos.x, pos.y, pos.z, 0.5, 255, 0, 0, 50) end end local function handleMovementMode() if not state.isPlacing or state.mode ~= 'movement' or not DoesEntityExist(state.currentEntity) then return end -- Disable player movement DisableControlAction(0, 30, true) -- Move Left/Right DisableControlAction(0, 31, true) -- Move Forward/Back DisableControlAction(0, 36, true) -- Duck DisableControlAction(0, 21, true) -- Sprint DisableControlAction(0, 22, true) -- Jump local coords = GetEntityCoords(state.currentEntity) local heading = GetEntityHeading(state.currentEntity) local moveSpeed = IsControlPressed(0, 21) and (state.settings.movementStepFast or 0.5) or (state.settings.movementStep or 0.1) -- Faster with shift local moved = false -- Get camera direction for relative movement local camRot = GetGameplayCamRot(2) local camHeading = math.rad(camRot.z) local camForward = vector3(-math.sin(camHeading), math.cos(camHeading), 0) local camRight = vector3(math.cos(camHeading), math.sin(camHeading), 0) -- WASD movement if IsControlPressed(0, 32) then -- W coords = coords + camForward * moveSpeed moved = true elseif IsControlPressed(0, 33) then -- S coords = coords - camForward * moveSpeed moved = true end if IsControlPressed(0, 34) then -- A coords = coords - camRight * moveSpeed moved = true elseif IsControlPressed(0, 35) then -- D coords = coords + camRight * moveSpeed moved = true end -- Vertical movement if state.settings.allowVertical then if IsControlPressed(0, 85) then -- Q coords = coords + vector3(0, 0, moveSpeed) moved = true elseif IsControlPressed(0, 48) then -- Z coords = coords + vector3(0, 0, -moveSpeed) moved = true end end -- Rotation if IsControlPressed(0, 174) then -- Left arrow heading = heading + 2.0 moved = true elseif IsControlPressed(0, 175) then -- Right arrow heading = heading - 2.0 moved = true end -- Apply changes if moved then SetEntityCoords(state.currentEntity, coords.x, coords.y, coords.z, false, false, false, true) SetEntityHeading(state.currentEntity, heading) end -- Switch to normal mode if state.settings.allowNormal and IsControlJustPressed(0, 38) then -- E state.mode = 'normal' SetEntityCollision(state.currentEntity, true, true) end -- Snap to ground if IsControlJustPressed(0, 19) then -- Alt PlaceObjectOnGroundProperly(state.currentEntity) end end local function placementLoop() CreateThread(function() while state.isPlacing do Wait(0) -- Handle input based on mode if state.mode == 'normal' then handleNormalMode() elseif state.mode == 'movement' then handleMovementMode() end -- Common controls using key mappings if state.keys.placeObject then state.keys.placeObject = false -- Reset the key state local canPlace = checkMaterialAndBoundary() if canPlace then Wait(100) local coords = GetEntityCoords(state.currentEntity) if not state.settings.allowVertical or state.snapToGround then local groundZ, _z = GetGroundZFor_3dCoord(coords.x, coords.y, coords.z + 50, false) if groundZ then coords = vector3(coords.x, coords.y, _z) end end local rotation = GetEntityRotation(state.currentEntity) if state.promise then state.promise:resolve({ entity = state.currentEntity, coords = coords, rotation = rotation, placed = true }) end PlaceableObject.Stop() break end end -- Cancel placement using key mapping if state.keys.cancelPlacement then state.keys.cancelPlacement = false -- Reset the key state if state.promise then state.promise:resolve(false) end PlaceableObject.Stop() break end -- Check if entity is outside boundary and cancel if so if state.settings.boundary and state.currentEntity then local canPlace = checkMaterialAndBoundary() if not canPlace then if state.promise then state.promise:resolve(false) end PlaceableObject.Stop() break end end -- Draw boundary if exists and enabled if state.settings.drawBoundary then drawBoundaryBox(state.settings.boundary) end -- Draw entity bounding box if enabled if state.settings.drawInBoundary and state.currentEntity then local overallResult, boundaryResult, customResult = checkMaterialAndBoundaryDetailed() -- Show red if any check fails, green if all pass local inBounds = overallResult drawEntityBoundingBox(state.currentEntity, inBounds) end -- Show help text for placement controls local placementText = { string.format(locale('placeable_object.place_object_place'), Bridge.Utility.GetCommandKey('+place_object')), string.format(locale('placeable_object.place_object_cancel'), Bridge.Utility.GetCommandKey('+cancel_placement')), -- string.format(locale('placeable_object.rotate_left'), Bridge.Utility.GetCommandKey('+rotate_left')), -- string.format(locale('placeable_object.rotate_right'), Bridge.Utility.GetCommandKey('+rotate_right')), string.format(locale('placeable_object.place_object_scroll_up'), Bridge.Utility.GetCommandKey('+scroll_up')), string.format(locale('placeable_object.place_object_scroll_down'), Bridge.Utility.GetCommandKey('+scroll_down')), string.format(locale('placeable_object.depth_modifier'), Bridge.Utility.GetCommandKey('+depth_modifier')) } Bridge.Notify.ShowHelpText(type(placementText) == 'table' and table.concat(placementText)) -- -- Draw entity bounding box -- drawEntityBoundingBox(state.currentEntity, checkMaterialAndBoundary()) -- -- Update instructional buttons -- if state.scaleform then -- Scaleform.RenderInstructionalButtons(state.scaleform) -- end end end) end -- Main functions ---@param model - Model name or hash ---@param settings - Configuration table: --[[ depth (3.0): Starting distance from player, allowVertical (false): Enable height controls, allowMovement (false): Enable WASD mode, disableSphere (false): Hide position indicator, boundary: Area restriction {min = vector3(), max = vector3()}, allowedMats: Surface materials {"concrete", "grass"}, depthStep (0.1): Step size for depth adjustment, rotationStep (0.5): Step size for rotation, heightStep (0.5): Step size for height adjustment, movementStep (0.1): Step size for normal movement, movementStepFast (0.5): Step size for fast movement (with shift), maxDepth (50.0): Maximum distance from player, --]] ---@returns Promise with: {entity, coords, heading, placed, cancelled} --[[ Example: local result = Citizen.Await(PlaceableObject.Create("prop_barrier_work05", { depth = 5.0, allowVertical = false })) --]] function PlaceableObject.Create(model, settings) if state.isPlacing then PlaceableObject.Stop() end -- Default settings settings = settings or {} settings.depth = settings.depth or 3.0 -- Start closer to player settings.allowVertical = settings.allowVertical or false settings.allowMovement = settings.allowMovement or false settings.allowNormal = settings.allowNormal or false settings.disableSphere = settings.disableSphere or false settings.drawBoundary = settings.drawBoundary or false settings.drawInBoundary = settings.drawInBoundary or false -- Movement speed settings settings.depthStep = settings.depthStep or 0.1 -- Fine control for depth adjustment settings.rotationStep = settings.rotationStep or 0.5 -- Normal rotation speed settings.heightStep = settings.heightStep or 0.5 -- Height adjustment speed settings.movementStep = settings.movementStep or 0.1 -- Normal movement speed settings.movementStepFast = settings.movementStepFast or 0.5 -- Fast movement speed (with shift) settings.maxDepth = settings.maxDepth or 5.0 -- Maximum distance from player state.settings = settings state.depth = settings.depth -- Use the settings depth state.heading = -GetEntityHeading(PlayerPedId()) state.height = 0.0 state.snapToGround = not settings.allowVertical state.mode = 'normal' local p = promise.new() state.promise = p local point = Bridge.ClientEntity.Create({ id = 'placeable_object', entityType = 'object', model = model, coords = GetEntityCoords(PlayerPedId()), rotation = vector3(0.0, 0.0, state.heading), OnSpawn= function(data) state.currentEntity = data.spawned SetEntityCollision(state.currentEntity, false, false) FreezeEntityPosition(state.currentEntity, false) -- Set initial position based on depth local playerPos = GetEntityCoords(PlayerPedId()) local forward = GetEntityForwardVector(PlayerPedId()) local spawnPos = playerPos + forward * state.depth SetEntityCoords(state.currentEntity, spawnPos.x, spawnPos.y, spawnPos.z + state.height, false, false, false, true) end, }) -- Setup instructional buttons -- state.scaleform = setupInstructionalButtons() state.scaleform = nil state.isPlacing = true -- Show help text for placement controls local placementText = { string.format(locale('placeable_object.place_object_place'), Bridge.Utility.GetCommandKey('+place_object')), string.format(locale('placeable_object.place_object_cancel'), Bridge.Utility.GetCommandKey('+cancel_placement')), -- string.format(locale('placeable_object.rotate_left'), Bridge.Utility.GetCommandKey('+rotate_left')), -- string.format(locale('placeable_object.rotate_right'), Bridge.Utility.GetCommandKey('+rotate_right')), string.format(locale('placeable_object.place_object_scroll_up'), Bridge.Utility.GetCommandKey('+scroll_up')), string.format(locale('placeable_object.place_object_scroll_down'), Bridge.Utility.GetCommandKey('+scroll_down')), string.format(locale('placeable_object.depth_modifier'), Bridge.Utility.GetCommandKey('+depth_modifier')) } Bridge.Notify.ShowHelpText(type(placementText) == 'table' and table.concat(placementText)) placementLoop() return Citizen.Await(p) end function PlaceableObject.Stop() Bridge.Notify.HideHelpText() if state.currentEntity and DoesEntityExist(state.currentEntity) then -- Disable entity outline before deleting SetEntityDrawOutline(state.currentEntity, false) DeleteObject(state.currentEntity) end if state.scaleform then Scaleform.Unload(state.scaleform) end ClientEntity.Unregister('placeable_object') -- Reset state state.isPlacing = false state.currentEntity = nil state.mode = 'normal' state.promise = nil state.scaleform = nil state.settings = {} return true end -- Status functions function PlaceableObject.IsPlacing() return state.isPlacing end function PlaceableObject.GetCurrentEntity() return state.currentEntity end function PlaceableObject.GetCurrentMode() return state.mode end AddEventHandler('onResourceStop', function(resource) if resource ~= GetCurrentResourceName() then return end if state.isPlacing then PlaceableObject.Stop() end end) return PlaceableObject