- gutted utilities to only have a couple useful functions
- `removed` second object spawner - `edit` more lib conversions
This commit is contained in:
1 parent
d85a1300d5
commit
3947eb6cd5
7 files changed
+23
-1118
No files matched your search
+2
-5
@@ -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,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
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,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
@@ -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
|
||||
Reference in new issue
Block a user