Skip to content

Object Placement System for RedM โ€‹

A flexible and reusable object placement system for RedM, allowing for precise object preview and placement with rotation and height adjustment.

Features โ€‹

  • Preview-based placement system with visual feedback
  • Camera-relative movement controls for intuitive positioning
  • Multi-axis rotation support with X-axis limits (-90ยฐ to 90ยฐ)
  • Height adjustment with mouse wheel
  • Put on Ground functionality (E key) - instantly snap objects to ground level
  • Automatic ground placement when objects are first spawned
  • Customizable placement settings
  • Custom placement handlers with validation support
  • Grouped prompt system for organized UI display
  • Player state management during placement
  • Automatic resource cleanup
  • Event system integration

Controls โ€‹

The placement system uses an intuitive control scheme organized into grouped prompts:

Movement Controls (Camera-Relative) โ€‹

  • Arrow Keys: Move object relative to camera direction
    • โ†‘ (Up Arrow): Move forward (away from camera)
    • โ†“ (Down Arrow): Move backward (toward camera)
    • โ† (Left Arrow): Move left relative to camera
    • โ†’ (Right Arrow): Move right relative to camera

Height Controls โ€‹

  • Mouse Wheel Up: Move object up
  • Mouse Wheel Down: Move object down

Rotation Controls โ€‹

  • 1 Key: Rotate X-axis positive (tilt forward)
  • 2 Key: Rotate X-axis negative (tilt backward)
  • 3 Key: Rotate Z-axis positive (turn right)
  • 4 Key: Rotate Z-axis negative (turn left)

Note: X-axis rotation is limited between -90ยฐ and 90ยฐ to prevent flipping

Action Controls โ€‹

  • Enter: Confirm placement
  • Backspace: Cancel placement
  • E Key: Put object on ground (snaps to ground level while preserving rotation)

All controls are displayed as organized prompt groups during placement for easy reference.

Basic Usage โ€‹

lua
local Placer = exports.bln_lib:ObjPlacer()

-- Simple object placement with creation
RegisterCommand('placeobject', function()
    Placer.StartPlacement('p_beechers_ladder01x', {
        handlers = {
            afterPlace = function(placementData)
                -- Create the actual object after placement is confirmed
                local object = CreateObject(
                    placementData.model,
                    placementData.position.x,
                    placementData.position.y,
                    placementData.position.z,
                    true, true, false
                )
                
                -- Set rotation
                SetEntityRotation(object, 
                    placementData.rotation.x,
                    placementData.rotation.y,
                    placementData.rotation.z,
                    2, true
                )
            end
        }
    })
end)

Custom Settings โ€‹

lua
local settings = {
    moveSpeed = 0.05,      -- Movement speed for arrow keys
    rotationSpeed = 3.0,   -- Rotation speed for 1-4 keys
    heightSpeed = 0.05,    -- Height adjustment speed for mouse wheel
    spawnDistance = 1.0,   -- Initial spawn distance from player
    spawnHeight = 0.0,     -- Initial spawn height offset
}

Placer.StartPlacement('p_beechers_ladder01x', settings)

Using Handlers โ€‹

The placement system supports four types of handlers:

  • beforePlace: Runs before placement confirmation, can prevent placement
  • onPlace: Runs during placement confirmation, can prevent placement
  • afterPlace: Runs after placement is confirmed, used for creating the actual object
  • onCancel: Runs when placement is cancelled or cleaning up
lua
local settings = {
    handlers = {
        beforePlace = function(placementData)
            -- Check conditions before placing
            local hasRequiredItems = exports.inventory:HasItems('required_item', 1)
            if not hasRequiredItems then
                Print("You need required items!")
                return false -- Prevents placement
            end
            return true -- Allows placement to continue
        end,
        
        onPlace = function(placementData)
            -- Run a minigame or skill check
            local success = exports.minigame:Start()
            return success -- Returns true/false to allow/prevent placement
        end,
        
        afterPlace = function(placementData)
            -- Create and setup the actual object
            local object = CreateObject(
                placementData.model,
                placementData.position,
                true, true, false
            )
            
            -- Additional setup...
            TriggerServerEvent('saveObject', placementData.position, placementData.rotation)
        end,
        
        onCancel = function(placementData)
            -- Handle cleanup or cancellation
            Print("Placement cancelled")
        end
    }
}

Placer.StartPlacement('p_beechers_ladder01x', settings)

Example: Using the Put on Ground Feature โ€‹

lua
RegisterCommand('placeitem', function()
    local Placer = exports.bln_lib:ObjPlacer()
    
    Placer.StartPlacement('p_chest01x', {
        handlers = {
            afterPlace = function(placementData)
                -- Object is already properly positioned on ground
                local object = CreateObject(
                    placementData.model,
                    placementData.position.x,
                    placementData.position.y,
                    placementData.position.z,
                    true, true, false
                )
                
                -- Apply the exact rotation from placement
                SetEntityRotation(object, 
                    placementData.rotation.x,
                    placementData.rotation.y,
                    placementData.rotation.z,
                    2, true
                )
                
                FreezeEntityPosition(object, true)
                TriggerServerEvent('saveObject', placementData)
            end
        }
    })
end)

Example: Placement with Validation โ€‹

lua
local settings = {
    handlers = {
        beforePlace = function(placementData)
            -- Check if player has required items
            local hasWood = exports.inventory:HasItem('wood', 5)
            local hasNails = exports.inventory:HasItem('nails', 10)
            
            if not hasWood or not hasNails then
                exports.notifications:Notify("You need 5 wood and 10 nails!", "error")
                return false -- Prevents placement
            end
            return true
        end,
        
        afterPlace = function(placementData)
            -- Remove items and create object
            exports.inventory:RemoveItem('wood', 5)
            exports.inventory:RemoveItem('nails', 10)
            
            local object = CreateObject(placementData.model, placementData.position, true, true, false)
            SetEntityRotation(object, placementData.rotation.x, 0.0, placementData.rotation.z, 2, true)
            
            exports.notifications:Notify("Structure built successfully!", "success")
        end
    }
}

Placer.StartPlacement('p_fence01x', settings)

API Reference โ€‹

Exports โ€‹

ObjPlacer โ€‹

Returns the placement handler with the following methods:

StartPlacement(objectModel, settings) โ€‹

Starts the placement preview mode for the specified object.

  • objectModel: String - The model name or hash of the object to place
  • settings: Table (optional) - Custom settings and handlers

Returns: Nothing (starts placement mode)

CancelPlacement() โ€‹

Cancels the current placement and cleans up preview objects.

Returns: Nothing

IsPlacing() โ€‹

Checks if placement mode is currently active.

Returns: Boolean - true if placing, false otherwise

GetPreviewData() โ€‹

Gets the current preview object data while in placement mode.

Returns: Table containing model, position, and rotation, or nil if not placing

lua
{
    model = hash,       -- Model hash of the preview object
    position = vector3, -- Current position
    rotation = vector3  -- Current rotation
}

Handler Data โ€‹

All handlers receive a placementData table containing:

lua
{
    model = hash,      -- Model hash of the object
    position = vector3, -- Final position coordinates
    rotation = vector3  -- Final rotation (x, y, z in degrees)
}

Events โ€‹

The system triggers the following events:

placement:completed โ€‹

Triggered when an object is successfully placed.

  • Data: placementData table (same structure as handler data)
lua
AddEventHandler('placement:completed', function(placementData)
    print("Object placed at:", placementData.position)
end)

Notes โ€‹

  • Preview objects are automatically created with visual feedback (pickup light effect)
  • Ground placement: Objects are automatically placed on ground when first spawned
  • Camera-relative movement: All movement controls work relative to your camera direction for intuitive control
  • Rotation limits: X-axis rotation is clamped between -90ยฐ and 90ยฐ to prevent object flipping
  • Put on Ground feature: Press E to instantly snap the object to ground level while preserving rotation
  • Player restrictions: Player movement and actions (aim, sprint, melee, jump) are disabled during placement
  • Automatic cleanup: Resources automatically clean up on stop to prevent memory leaks
  • Handler flow control: beforePlace and onPlace handlers can return false to prevent placement
  • Grouped prompts: Controls are organized into logical groups for better UI organization
  • Event integration: Successful placements trigger the 'placement:completed' event

Complete Example Implementation โ€‹

Here's a complete example of a ladder placement system with all features:

lua
local Placer = exports.bln_lib:ObjPlacer()
local ladderEntity = nil

local Settings = {
    moveSpeed = 0.08,      -- Slightly faster movement
    rotationSpeed = 2.5,   -- Smooth rotation
    heightSpeed = 0.1,     -- Quick height adjustment
    spawnDistance = 2.0,   -- Spawn further from player
    spawnHeight = 0.0,     -- Start on ground
    handlers = {
        beforePlace = function(placementData)
            -- Check if player has required items
            local hasWood = exports.inventory:HasItem('wood', 3)
            if not hasWood then
                exports.notifications:Notify("You need 3 wood to build a ladder!", "error")
                return false
            end
            return true
        end,
        
        onPlace = function(placementData)
            -- Optional: Add a skill check or minigame
            exports.notifications:Notify("Building ladder...", "info")
            return true
        end,
        
        afterPlace = function(placementData)
            -- Remove materials
            exports.inventory:RemoveItem('wood', 3)
            
            -- Create the actual ladder with exact placement data
            local object = CreateObject(
                placementData.model,
                placementData.position.x,
                placementData.position.y,
                placementData.position.z,
                true, true, false
            )

            -- Apply rotation exactly as placed
            SetEntityRotation(object, 
                placementData.rotation.x,
                placementData.rotation.y,
                placementData.rotation.z,
                2, true
            )

            ladderEntity = object
            FreezeEntityPosition(object, true)
            
            -- Save to server
            TriggerServerEvent('ladder:save', placementData)
            exports.notifications:Notify("Ladder built successfully!", "success")
        end,
        
        onCancel = function(placementData)
            exports.notifications:Notify("Ladder placement cancelled", "warning")
            -- Clean up any existing ladder if rebuilding
            if ladderEntity then
                DeleteObject(ladderEntity)
                ladderEntity = nil
            end
        end
    }
}

-- Command to place/remove ladder
RegisterCommand('ladder', function()
    -- If already placing or ladder exists, cancel/remove
    if ladderEntity or Placer.IsPlacing() then
        if ladderEntity then
            DeleteObject(ladderEntity)
            ladderEntity = nil
            exports.notifications:Notify("Ladder removed", "info")
        end
        if Placer.IsPlacing() then
            Placer.CancelPlacement()
        end
        return
    end

    -- Start new placement
    Placer.StartPlacement('p_beechers_ladder01x', Settings)
end)

-- Handle placement completion event
AddEventHandler('placement:completed', function(placementData)
    print("Ladder placed successfully at:", json.encode(placementData.position))
end)

-- Cleanup on resource stop
AddEventHandler('onResourceStop', function(resourceName)
    if GetCurrentResourceName() ~= resourceName then return end
    if ladderEntity then
        DeleteObject(ladderEntity)
    end
end)

Pro Tips โ€‹

  1. Use the E key to quickly snap objects to ground level while preserving your rotation
  2. Camera-relative movement makes positioning intuitive - face the direction you want to move the object
  3. Group similar placements by using the same Settings table for consistency
  4. Validate in beforePlace to check requirements before entering the minigame/animation phase
  5. Use GetPreviewData() in handlers to access real-time placement information
  6. Handle the placement:completed event for logging or additional processing