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 โ
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 โ
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 placementonPlace
: Runs during placement confirmation, can prevent placementafterPlace
: Runs after placement is confirmed, used for creating the actual objectonCancel
: Runs when placement is cancelled or cleaning up
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 โ
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 โ
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 placesettings
: 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
{
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:
{
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)
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:
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 โ
- Use the E key to quickly snap objects to ground level while preserving your rotation
- Camera-relative movement makes positioning intuitive - face the direction you want to move the object
- Group similar placements by using the same Settings table for consistency
- Validate in beforePlace to check requirements before entering the minigame/animation phase
- Use GetPreviewData() in handlers to access real-time placement information
- Handle the placement:completed event for logging or additional processing