- gutted utilities to only have a couple useful functions

- `removed` second object spawner
- `edit` more lib conversions
This commit is contained in:
Mustachedom committed 2026-08-18 01:23:06 -07:00
1 parent d85a1300d5
commit 3947eb6cd5
7 files changed
+23 -1118

No files matched your search

+2 -5
View File
@@ -11,14 +11,13 @@ shared_scripts {
'@ox_lib/init.lua',
'settings/sharedConfig.lua',
'lib/init.lua',
'modules/locales/*.lua',
'modules/clothing/**/shared.lua',
}
server_scripts {
'@oxmysql/lib/MySQL.lua',
'settings/serverConfig.lua',
'modules/locales/shared.lua',
'modules/version/server/*.lua',
'modules/version/server/*.lua',
'modules/framework/**/server.lua',
'modules/inventory/**/server.lua',
@@ -33,13 +32,12 @@ server_scripts {
'modules/housing/**/server.lua',
'modules/skills/**/server.lua',
'modules/bossmenu/**/server.lua',
"lib/**/server.lua",
"lib/**/server/**.lua",
'init.lua',
}
client_scripts {
'settings/clientConfig.lua',
'modules/locales/shared.lua',
'modules/framework/**/client.lua',
'modules/inventory/**/client.lua',
'modules/doorlock/**/client.lua',
@@ -60,7 +58,6 @@ client_scripts {
'modules/housing/**/client.lua',
'modules/skills/**/client.lua',
'modules/bossmenu/**/client.lua',
'modules/zones/**/client.lua',
'init.lua',
}
+1 -2
View File
@@ -1,4 +1,3 @@
Utility = Utility or Require("lib/utility/client/utility.lua")
Point = Point or Require("lib/points/client/points.lua")
ClientEntity = {
All = {},
@@ -74,7 +73,7 @@ end
local function SpawnEntity(entityData)
if entityData.spawned and DoesEntityExist(entityData.spawned) then return end -- Already spawned
if entityData.model then
local loaded, model = Utility.LoadModel(entityData.model)
local loaded, model = lib.requestModel(entityData.model)
if not loaded then
print(string.format("[ClientEntity] Failed to load model %s for entity %s", entityData.model, entityData.id))
return
-1
View File
@@ -53,7 +53,6 @@ if IsDuplicityVersion() then return cLib end
cLib.Placeable = Placeable or Require("lib/placers/client/object_placer.lua")
cLib.Utility = Utility or Require("lib/utility/client/utility.lua")
cLib.PlaceableObject = ObjectPlacer or Require("lib/placers/client/placeable_object.lua")
cLib.Point = Point or Require("lib/points/client/points.lua")
cLib.Entity = ClientEntity or Require("lib/entities/client/client_entity.lua")
+7 -8
View File
@@ -1,17 +1,16 @@
Placeable = Placeable or {}
Utility = Utility or Require("lib/utility/client/utility.lua")
if not lib then Require('init.lua', 'ox_lib') end
local activePlacementProp = nil
-- Object placer --
local placementText = {
lib.locale('placeable_object.place_object_place'),
lib.locale('placeable_object.place_object_cancel'),
lib.locale('placeable_object.place_object_scroll_up'),
lib.locale('placeable_object.place_object_scroll_down')
}
-- local placementText = {
-- lib.locale('placeable_object.place_object_place'),
-- lib.locale('placeable_object.place_object_cancel'),
-- lib.locale('placeable_object.place_object_scroll_up'),
-- lib.locale('placeable_object.place_object_scroll_down')
-- }
local function finishPlacing()
Bridge.Notify.HideHelpText()
@@ -39,7 +38,7 @@ Placeable.PlaceObject = function(object, distance, snapToGround, allowedMats, of
local checkDist = distance or 10.0
local ped = PlayerPedId()
Utility.LoadModel(propObject)
lib.requestModel(propObject)
activePlacementProp = CreateObject(propObject, 1.0, 1.0, 1.0, false, true, true)
SetModelAsNoLongerNeeded(propObject)
-699
View File
@@ -1,699 +0,0 @@
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")
PlaceableObject = PlaceableObject or {}
-- Register key mappings for placement controls
RegisterKeyMapping('+place_object', lib.locale('placeable_object.object_place'), 'mouse_button', 'MOUSE_LEFT')
RegisterKeyMapping('+cancel_placement', lib.locale('placeable_object.object_cancel'), 'mouse_button', 'MOUSE_RIGHT')
RegisterKeyMapping('+rotate_left', lib.locale('placeable_object.rotate_left'), 'keyboard', 'LEFT')
RegisterKeyMapping('+rotate_right', lib.locale('placeable_object.rotate_right'), 'keyboard', 'RIGHT')
RegisterKeyMapping('+scroll_up', lib.locale('placeable_object.object_scroll_up'), 'mouse_wheel', 'IOM_WHEEL_UP')
RegisterKeyMapping('+scroll_down', lib.locale('placeable_object.object_scroll_down'), 'mouse_wheel', 'IOM_WHEEL_DOWN')
RegisterKeyMapping('+depth_modifier', lib.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
}
}
local placementText = {
string.format(lib.locale('placeable_object.object_place'), Utility.GetCommandKey('+place_object')),
string.format(lib.locale('placeable_object.object_cancel'), Utility.GetCommandKey('+cancel_placement')),
string.format(lib.locale('placeable_object.rotate_clockwise'), Utility.GetCommandKey('+scroll_up')),
string.format(lib.locale('placeable_object.rotate_counter_clockwise'), Utility.GetCommandKey('+scroll_down')),
string.format(lib.locale('placeable_object.depth_modifier'), Utility.GetCommandKey('+depth_modifier'))
}
-- 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 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 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
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
-- end
-- end
coords = vector3(coords.x, coords.y, coords.z - 0.2) -- Adjust Z to ground level
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 (Q/E, Alt to toggle ground snap)
allowMovement = false, -- Enable WASD movement mode
allowNormal = false, -- Allow switching back to normal mode from movement
disableSphere = false, -- Hide position indicator sphere
drawBoundary = false, -- Draw boundary box/polygon
drawInBoundary = false, -- Draw entity outline (green/red) based on boundary/material
boundary = nil, -- Area restriction (box: {min=vector3(), max=vector3()}, polygon: {points={...}, minZ, maxZ})
allowedMats = nil, -- Allowed surface materials (e.g. {"concrete", "grass"})
customCheck = nil, -- Custom function(pos, entity, settings) -> bool
depthStep = 0.1, -- Step size for depth adjustment (scroll + modifier)
rotationStep = 0.5, -- Step size for rotation (scroll)
heightStep = 0.5, -- Step size for height adjustment (Q/E)
movementStep = 0.1, -- Step size for WASD movement
movementStepFast = 0.5, -- Step size for fast movement (WASD + Shift)
maxDepth = 5.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
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
+1 -400
View File
@@ -1,51 +1,8 @@
Utility = Utility or {}
local blipIDs = {}
local spawnedPeds = {}
local spawnedProps = {}
Point = Point or Require('lib/points/client/points.lua')
---Get the hash of a model (string or number)
---@param model string|number
---@return number
local function getModelHash(model)
if type(model) ~= 'number' then
return joaat(model)
end
return model
end
---Ensure a model is loaded into memory
---@param model string|number
---@return boolean, number
local function ensureModelLoaded(model)
local hash = getModelHash(model)
if HasModelLoaded(hash) then return true, hash end
if not IsModelValid(hash) and not IsModelInCdimage(hash) then return false, hash end
RequestModel(hash)
local count = 0
while not HasModelLoaded(hash) and count < 30000 do
Wait(0)
count = count + 1
end
return HasModelLoaded(hash), hash
end
---Create a prop with the given model and coordinates
---@param model string|number
---@param coords vector3
---@param heading number
---@param networked boolean
---@return number|nil
function Utility.CreateProp(model, coords, heading, networked)
local loaded, hash = ensureModelLoaded(model)
if not loaded then return nil, print("^6 community_bridge model was unable to load "..tostring(model).." ^0") end
local propEntity = CreateObject(hash, coords.x, coords.y, coords.z, networked, false, false)
SetEntityHeading(propEntity, heading)
SetModelAsNoLongerNeeded(hash)
spawnedProps[tostring(propEntity)] = true
return propEntity
end
---Get street and crossing names at given coordinates
---@param coords vector3
@@ -55,60 +12,6 @@ function Utility.GetStreetNameAtCoords(coords)
return GetStreetNameFromHashKey(streetHash), GetStreetNameFromHashKey(crossingHash)
end
---Create a vehicle with the given model and coordinates
---@param model string|number
---@param coords vector3
---@param heading number
---@param networked boolean
---@return number|nil, table
function Utility.CreateVehicle(model, coords, heading, networked)
local loaded, hash = ensureModelLoaded(model)
if not loaded then return nil, {}, print("^6 community_bridge model was unable to load "..tostring(model).." ^0") end
local vehicle = CreateVehicle(hash, coords.x, coords.y, coords.z, heading, networked, false)
SetVehicleHasBeenOwnedByPlayer(vehicle, true)
SetVehicleNeedsToBeHotwired(vehicle, false)
SetVehRadioStation(vehicle, "OFF")
SetModelAsNoLongerNeeded(hash)
return vehicle, {networkid = NetworkGetNetworkIdFromEntity(vehicle) or 0,coords = GetEntityCoords(vehicle),heading = GetEntityHeading(vehicle),}
end
---Create a ped with the given model and coordinates
---@param model string|number
---@param coords vector3
---@param heading number
---@param networked boolean
---@param settings table|nil
---@return number|nil
function Utility.CreatePed(model, coords, heading, networked, settings)
local loaded, hash = ensureModelLoaded(model)
if not loaded then return nil, print("^6 community_bridge model was unable to load "..tostring(model).." ^0") end
local spawnedEntity = CreatePed(0, hash, coords.x, coords.y, coords.z, heading, networked, false)
SetModelAsNoLongerNeeded(hash)
spawnedPeds[tostring(spawnedEntity)] = true
return spawnedEntity
end
---Show a busy spinner with the given text
---@param text string
---@return boolean
function Utility.StartBusySpinner(text)
AddTextEntry(text, text)
BeginTextCommandBusyString(text)
AddTextComponentSubstringPlayerName(text)
EndTextCommandBusyString(0)
return true
end
---Stop the busy spinner if active
---@return boolean
function Utility.StopBusySpinner()
if BusyspinnerIsOn() then
BusyspinnerOff()
return true
end
return false
end
---Create a blip at the given coordinates
---@param coords vector3
---@param sprite number
@@ -179,135 +82,6 @@ function Utility.RemoveBlip(blip)
return true
end
---Load a model into memory
---@param model string|number
---@return boolean, number
function Utility.LoadModel(model)
return ensureModelLoaded(model)
end
---Request an animation dictionary
---@param dict string
---@return boolean
function Utility.RequestAnimDict(dict)
if HasAnimDictLoaded(dict) then return true end
RequestAnimDict(dict)
local count = 0
while not HasAnimDictLoaded(dict) and count < 30000 do
Wait(0)
count = count + 1
end
return HasAnimDictLoaded(dict)
end
---Remove a ped if it exists
---@param entity number
---@return boolean
function Utility.RemovePed(entity)
if not spawnedPeds[tostring(entity)] then return false end
spawnedPeds[tostring(entity)] = nil
if not DoesEntityExist(entity) then return false end
SetEntityAsMissionEntity(entity, true, true)
DeleteEntity(entity)
return true
end
---Remove a ped if it exists
---@param entity number
---@return boolean
function Utility.RemoveProp(entity)
if not spawnedProps[tostring(entity)] then return false end
spawnedProps[tostring(entity)] = nil
if not DoesEntityExist(entity) then return false end
SetEntityAsMissionEntity(entity, true, true)
DeleteEntity(entity)
return true
end
---Show a native input menu and return the result
---@param text string
---@param length number
---@return string|boolean
function Utility.NativeInputMenu(text, length)
local maxLength = Math and Math.Clamp and Math.Clamp(length, 1, 50) or math.min(math.max(length or 10, 1), 50)
local menuText = text or 'enter text'
AddTextEntry(menuText, menuText)
DisplayOnscreenKeyboard(1, menuText, "", "", "", "", "", maxLength)
while (UpdateOnscreenKeyboard() == 0) do
DisableAllControlActions(0)
Wait(0)
end
if (GetOnscreenKeyboardResult()) then
return GetOnscreenKeyboardResult()
end
return false
end
---Get the skin data of a ped
---@param entity number
---@return table
function Utility.GetEntitySkinData(entity)
local skinData = { clothing = {}, props = {} }
for i = 0, 11 do
skinData.clothing[i] = { GetPedDrawableVariation(entity, i), GetPedTextureVariation(entity, i) }
end
for i = 0, 13 do
skinData.props[i] = { GetPedPropIndex(entity, i), GetPedPropTextureIndex(entity, i) }
end
return skinData
end
---Apply skin data to a ped
---@param entity number
---@param skinData table
---@return boolean
function Utility.SetEntitySkinData(entity, skinData)
for i = 0, 11 do
SetPedComponentVariation(entity, i, skinData.clothing[i][1], skinData.clothing[i][2], 0)
end
for i = 0, 13 do
SetPedPropIndex(entity, i, skinData.props[i][1], skinData.props[i][2], 0)
end
return true
end
---Reload the player's skin and remove attached objects
---@return boolean
function Utility.ReloadSkin()
local ped = PlayerPedId()
local skinData = Utility.GetEntitySkinData(ped)
Utility.SetEntitySkinData(ped, skinData)
local poolObject = GetGamePool("CObject")
for _, props in pairs(poolObject) do
if IsEntityAttachedToEntity(ped, props) then
SetEntityAsMissionEntity(props, true, true)
DeleteObject(props)
DeleteEntity(props)
end
end
return true
end
---Show a native help text
---@param text string
---@param duration number
function Utility.HelpText(text, duration)
AddTextEntry(text, text)
BeginTextCommandDisplayHelp(text)
EndTextCommandDisplayHelp(0, false, true, duration or 5000)
end
---Show a floating help text in the world
---@param text string The text to show
---@param coords table The coords to show the message at
---@return nil
function Utility.FloatingHelpText(text, coords)
AddTextEntry("community_bridge_"..text, text)
SetFloatingHelpTextWorldPosition(1, coords.x, coords.y, coords.z)
SetFloatingHelpTextStyle(1, 1, 2, -1, 3, 0)
BeginTextCommandDisplayHelp("community_bridge_"..text)
EndTextCommandDisplayHelp(2, false, false, 100)
end
---Draw 3D help text in the world
---@param coords vector3
@@ -343,113 +117,7 @@ end
function Utility.Draw3DHelpTextExt(coords, text, scale)
end
---Show a native notification
---@param text string
function Utility.NotifyText(text)
AddTextEntry(text, text)
SetNotificationTextEntry(text)
DrawNotification(false, true)
end
---Teleport the player to given coordinates
---@param coords vector3
---@param conditionFunction function|nil
---@param afterTeleportFunction function|nil
function Utility.TeleportPlayer(coords, conditionFunction, afterTeleportFunction)
local ped = PlayerPedId()
if conditionFunction ~= nil then
if not conditionFunction() then
return
end
end
DoScreenFadeOut(2500)
Wait(2500)
SetEntityCoords(ped, coords.x, coords.y, coords.z, false, false, false, false)
if coords.w then SetEntityHeading(ped, coords.w) end
FreezeEntityPosition(ped, true)
local count = 0
while not HasCollisionLoadedAroundEntity(ped) and count <= 30000 do
RequestCollisionAtCoord(coords.x, coords.y, coords.z)
Wait(0)
count = count + 1
end
FreezeEntityPosition(ped, false)
DoScreenFadeIn(1000)
if afterTeleportFunction ~= nil then
afterTeleportFunction()
end
end
---Get the hash from a model
---@param model string|number
---@return number
function Utility.GetEntityHashFromModel(model)
return getModelHash(model)
end
---Get the closest player to given coordinates
---@param coords vector3|nil
---@param distanceScope number|nil
---@param includeMe boolean|nil
---@return number, number, number
function Utility.GetClosestPlayer(coords, distanceScope, includeMe)
local players = GetActivePlayers()
local closestPlayer = 0
local selfPed = PlayerPedId()
local selfCoords = coords or GetEntityCoords(selfPed)
local closestDistance = distanceScope or 5
for _, player in ipairs(players) do
local playerPed = GetPlayerPed(player)
if includeMe or playerPed ~= selfPed then
local playerCoords = GetEntityCoords(playerPed)
local distance = #(selfCoords - playerCoords)
if closestDistance == -1 or distance < closestDistance then
closestPlayer = player
closestDistance = distance
end
end
end
return closestPlayer, closestDistance, GetPlayerServerId(closestPlayer)
end
---Get the closest vehicle to given coordinates
---@param coords vector3|nil
---@param distanceScope number|nil
---@param includePlayerVeh boolean|nil
---@return number|nil, vector3|nil, number|nil
function Utility.GetClosestVehicle(coords, distanceScope, includePlayerVeh)
local vehicleEntity = nil
local vehicleNetID = nil
local vehicleCoords = nil
local ped = PlayerPedId()
local selfCoords = coords or GetEntityCoords(ped)
local closestDistance = distanceScope or 5
local includeMyVeh = includePlayerVeh or false
local gamePoolVehicles = GetGamePool("CVehicle")
local playerVehicle = IsPedInAnyVehicle(ped, false) and GetVehiclePedIsIn(ped, false) or 0
for i = 1, #gamePoolVehicles do
local thisVehicle = gamePoolVehicles[i]
if DoesEntityExist(thisVehicle) and (includeMyVeh or thisVehicle ~= playerVehicle) then
local thisVehicleCoords = GetEntityCoords(thisVehicle)
local distance = #(selfCoords - thisVehicleCoords)
if closestDistance == -1 or distance < closestDistance then
vehicleEntity = thisVehicle
vehicleNetID = NetworkGetNetworkIdFromEntity(thisVehicle) or nil
vehicleCoords = thisVehicleCoords
closestDistance = distance
end
end
end
return vehicleEntity, vehicleCoords, vehicleNetID
end
-- Deprecated point functions (no changes) 12/14/25
5
function Utility.RegisterPoint(pointID, pointCoords, pointDistance, _onEnter, _onExit)
--print("^6 community_bridge ^: ^3Utility.RegisterPoint is deprecated. Please use Point.Register instead. ^0")
return Point.Register(pointID, pointCoords, pointDistance, nil, _onEnter, _onExit)
@@ -470,63 +138,6 @@ function Utility.RemovePoint(pointID)
return Point.Remove(pointID)
end
---Simple switch-case function
---@generic T
---@param value T The value to match against the cases
---@param cases table<T|false, fun(): any> Table with case functions and an optional default (false key)
---@return any|false result The return value of the matched case function, or false if none matched
function Utility.Switch(value, cases)
local caseFunc = cases[value] or cases[false]
if caseFunc and type(caseFunc) == "function" then
local ok, result = pcall(caseFunc)
return ok and result or false
end
return false
end
function Utility.CopyToClipboard(text)
if not text then return false end
if type(text) ~= "string" then
text = json.encode(text, { indent = true })
end
SendNUIMessage({
type = "copytoclipboard",
text = text
})
local message = lib.locale("clipboard.copy")
--TriggerEvent('community_bridge:Client:Notify', message, 'success')
return true
end
--- Pattern match-like function
---@generic T
---@param value T The value to match
---@param patterns table<T|fun(T):boolean|false, fun(): any> A list of matchers and their handlers
---@return any|false result The result of the first matched case, or false if none
function Utility.Match(value, patterns)
for pattern, handler in pairs(patterns) do
if type(pattern) == "function" then
local ok, matched = pcall(pattern, value)
if ok and matched then
local success, result = pcall(handler)
return success and result or false
end
elseif pattern == value then
local success, result = pcall(handler)
return success and result or false
end
end
if patterns[false] then
local ok, result = pcall(patterns[false])
return ok and result or false
end
return false
end
---Get zone name at coordinates
---@param coords vector3
---@return string
@@ -623,16 +234,6 @@ AddEventHandler('onResourceStop', function(resource)
RemoveBlip(tonumber(blip))
end
end
for _, ped in pairs(spawnedPeds) do
if ped and DoesEntityExist(tonumber(ped)) then
DeleteEntity(tonumber(ped))
end
end
for _, prop in pairs(spawnedProps) do
if prop and DoesEntityExist(tonumber(prop)) then
DeleteEntity(tonumber(prop))
end
end
end)
exports('Utility', Utility)
+12 -3
View File
@@ -1,3 +1,5 @@
> **Note:** This is **not intended to be a mergable PR as-is**. This is primarily a conversation starter to determine whether this direction is a good fit for the organization and the future of Community Bridge, or whether these changes would be better suited as a separate fork.
I think Community Bridge has been a huge benefit to the FiveM ecosystem and has helped a lot of creators support different frameworks without having to reinvent the same integrations over and over.
That said, I think the scope of the project has gradually become too broad. A number of the library modules are now duplicating functionality that already exists in `ox_lib`, which is already a required dependency of Community Bridge.
@@ -15,8 +17,9 @@ This PR removes the following `lib` modules:
* `sql` — effectively another abstraction over `oxmysql` without providing ORM-level functionality
* `vehicles` — unnecessary given existing `ox_lib` functionality
* `cutscenes` — extremely niche and doesn't justify being maintained as part of the bridge
* `client_entity_actions_ext` — while this could be a nice module, it leans into the idea of making this a library + bridge and gives the project too wide of a scope
I've also removed the `zones` and `locale` module.
I've also removed the `zones` and `locale` modules.
PolyZone has been around for a long time and was a perfectly reasonable solution when there weren't many alternatives. However, it hasn't received a meaningful update in years, and I don't think we should continue building abstractions around it when `ox_lib` already provides a modern zone implementation.
@@ -24,11 +27,17 @@ Since `ox_lib` is already a required dependency of Community Bridge, I'd rather
If someone eventually develops a zone library that is genuinely better or provides functionality that `ox_lib` doesn't, I'm completely open to revisiting this decision. But right now, I don't think there's a strong reason to maintain a bridge layer around PolyZone.
As for locales, ox already does a really great job with locales, and we should be utilizing this as well.
# footnote
As for locales, `ox_lib` already does a really great job with locales, and we should be utilizing this as well.
# Footnote
The goal here isn't to remove functionality for the sake of removing it. It's to establish a much narrower scope for Community Bridge and avoid turning it into a general-purpose library.
I'd rather see the bridge focus its maintenance and development effort on framework/resource compatibility — the part that actually benefits from having a bridge — while leaving general-purpose functionality to libraries that already specialize in it.
Again, **this PR is not meant to be merged immediately**. I'd like to use it as a starting point for discussion about what Community Bridge should actually be responsible for going forward. If this direction doesn't fit the organization's goals for the project, that's completely fine; I'd rather establish that now and potentially pursue these changes as a separate fork than continue moving the project in a direction that doesn't align with its intended purpose.
If there's a legitimate use case for any of these modules that I'm overlooking, I'm absolutely open to discussing it. The main thing I'm trying to avoid is maintaining duplicate abstractions simply because they exist.
p.s. no Ai use in the my removals / additions as code, but im a stupid dude and english isnt my first language (moron is) so i used Ai to pretty up the words here