Newb_BridgeUtilities

Utilities

Standalone exports from exports.Newb_Bridge. These are separate from the bridge.* modules but ship with the same resource.


AddInteraction(id, payload) (client)

Signature: AddInteraction(id: string, payload: InteractionPayload): boolean, string?

Registers a distance-based world point that spawns a ped or prop when the player enters range. Target options are wired automatically. Ped vs prop is detected from the model. radius defaults to 100 and is clamped to 1–175.

ParamTypeDescription
idstringUnique interaction id
payloadInteractionPayloadSpawn and target config

| Returns | true on success; false, 'missing' (no id/payload) or false, 'coords' (invalid coords). An existing id is replaced, not rejected. |

exports.Newb_Bridge:AddInteraction('pharmacy_clerk', {
    model = 's_m_m_doctor_01',
    coords = vec3(100.0, 200.0, 30.0),
    heading = 90.0,
    radius = 50.0,
    options = {
        {
            label = 'Talk',
            icon = 'fa-solid fa-comments',
            distance = 2.0,
            onSelect = function(data) end,
        },
    },
    onSpawn = function(interaction)
        SetEntityInvincible(interaction.entity, true)
    end,
})

RemoveInteraction(id, target?)

Signature: RemoveInteraction(id: string, target?: number): boolean

| Side | client removes locally; server triggers Newb_Bridge:Interaction:Remove | | Returns | boolean |

Server target is an optional player source. Omit it to broadcast to every client. A bad target does not fall back to broadcast.

-- Client
exports.Newb_Bridge:RemoveInteraction('pharmacy_clerk')
 
-- Server (all clients)
exports.Newb_Bridge:RemoveInteraction('pharmacy_clerk')
 
-- Server (one player)
exports.Newb_Bridge:RemoveInteraction('pharmacy_clerk', src)

UpdateInteraction(id, updates) (client)

Signature: UpdateInteraction(id: string, updates: Partial<InteractionPayload>): boolean

| Returns | boolean |

Updatable fields: model, heading, scenario, anim, component, weapon, options, onEnter, canSpawn, coords, radius, bucket. A model change respawns the entity.

exports.Newb_Bridge:UpdateInteraction('pharmacy_clerk', {
    heading = 180.0,
    options = { { label = 'Closed', disabled = true } },
})

RemoveAllInteractions() (client)

Signature: RemoveAllInteractions(): void

Removes every interaction registered by the current resource.

exports.Newb_Bridge:RemoveAllInteractions()

GetClosestInteraction() (client)

Signature: GetClosestInteraction(): InteractionSummary?

| Returns | InteractionSummary? |

No parameters. Uses the local player’s position.

local closest = exports.Newb_Bridge:GetClosestInteraction()
if closest and closest.distance < 5.0 then
    print('Nearest:', closest.id)
end

GetNearbyInteractions() (client)

Signature: GetNearbyInteractions(): InteractionSummary[]

| Returns | InteractionSummary[] — sorted by distance |

for _, interaction in ipairs(exports.Newb_Bridge:GetNearbyInteractions()) do
    print(interaction.id, interaction.distance)
end

GetInteractionByEntity(entity) (client)

Signature: GetInteractionByEntity(entity: number): string?, Interaction?

Reverse-lookup by the spawned prop/ped entity handle.

| Returns | id, interaction table — or nil if not managed |

local id = exports.Newb_Bridge:GetInteractionByEntity(entity)
if id then
    exports.Newb_Bridge:RemoveInteraction(id)
end

AddInteractionsBatch(entries, options?) (client)

Signature: AddInteractionsBatch(entries: InteractionBatchEntry[] | table<string, InteractionPayload>, options?: InteractionBatchOptions): number

Registers many interactions across frames so large sets (shops, hives, businesses) do not hitch on join. Returns a generation token used by CancelInteractionBatch.

Defaults (hardcoded in utility/client/interactions.lua): batchSize 25, batchDelayMs 0, batchThreshold 1, sortByDistance true.

local batch = {
    { id = 'hive_1', payload = { model = `prop_beehive`, coords = vec1, heading = 0.0, radius = 175.0, options = opts } },
    { id = 'hive_2', payload = { model = `prop_beehive`, coords = vec2, heading = 90.0, radius = 175.0, options = opts } },
}
 
exports.Newb_Bridge:AddInteractionsBatch(batch, {
    batchSize = 25,
    onComplete = function(added, failed)
        lib.print.debug(('Registered %s (%s failed)'):format(added, failed))
    end,
})
 
-- Map form also works:
exports.Newb_Bridge:AddInteractionsBatch({
    ['shop_register'] = { model = `prop_till_01`, coords = shopVec, heading = 0.0, radius = 40.0, options = opts },
})

CancelInteractionBatch() (client)

Abort the active batch before starting a fresh sync.

exports.Newb_Bridge:CancelInteractionBatch()

BeginInteractionBulk() / CommitInteractionBulk() (client)

Suppress per-point point-manager rebuilds while registering many interactions synchronously, then flush once.

exports.Newb_Bridge:BeginInteractionBulk()
for i = 1, #entries do
    exports.Newb_Bridge:AddInteraction(entries[i].id, entries[i].payload)
end
exports.Newb_Bridge:CommitInteractionBulk()

Prefer AddInteractionsBatch for large sets that should spread across frames.


PropPlacer(model, ...) (client)

Signature: PropPlacer(model: string | number | string[] | number[] | PlacerCallOptions, forceGround?: boolean, allowedMaterials?: table<number, boolean>, maxDistance?: number, placementType?: string, marker?: PlacerMarker, previewBehavior?: table, options?: PlacerOptions): vector4 | PlacerResult | PlacerMultiResult | false

Interactive prop/ped/vehicle placement UI. Returns placement coords, or false if cancelled, already placing, or the model failed to load.

Positional arguments work as before, or pass everything in one table — a model key selects the table form:

local placement = exports.Newb_Bridge:PropPlacer({
    model = 'jim_g_beehive_prop_2',
    forceGround = true, -- alias: snapToGround
    maxDistance = 10.0,
    heightAdjust = false,
})
FieldTypeDefaultDescription
modelstring | number | arrayModel, or list of models (↑/↓ cycles them)
forceGroundboolean?Snap the preview to the ground (alias: snapToGround)
allowedMaterialstable<number, boolean>?Raycast material whitelist
maxDistancenumber?10.0Max distance from player
placementType'auto' | 'ped' | 'prop' | 'vehicle''auto'Preview entity type (alias: type)
markertable?Head marker over the preview (see PlacerMarker below). Also accepted inside options
previewBehaviortable?Ped scenario/anim preview, or { options = {...} } cycle
multiboolean?falseKeep placing until Backspace; returns { multi = true, results = {...} }
maxPlacementsnumber?Cap for multi place; shows placed / max in the panel and auto-finishes
heightAdjustboolean?truefalse pins the preview to ground height and hides raise/lower
collisionToggleboolean?falseG opens the transform gizmo on the preview
startMode'position' | 'model' | 'animation'Adjustment mode the placer opens in
startHeadingnumber?player headingInitial preview heading
groundOffsetboolean | number | 'measure'-min.zVisual lift of the prop preview. Default puts the model’s render-box bottom on the surface; false pins the pivot to the surface; a number is an explicit lift; 'measure' grounds the preview with PlaceObjectOnGroundProperly like the placed prop (for models whose render box doesn’t match the mesh, e.g. the moonshine still or beehive). Returned coords stay at the aimed surface point — CreateObject re-applies the model-bottom offset at spawn
rotateStepnumber | table5Degrees per scroll notch; a table can override fine (Alt, 1) and coarse (Shift, 22.5) too
returnCancelboolean?falseH also cancels (return-to-menu flows)
isPositionValidfunction?fun(coords): boolean polled while placing; false blocks placement
localeOverridestable?Override any placer UI string for this session
placedMarkertable?{ radius, colour } marker on each multi placement
placedPreviewtable?{ model, existing } persistent preview entities at placed spots

Options may also be nested under an options key instead of sitting flat on the table.

PlacerMarker

The marker is always a table. preset pulls in the stock shape; any explicit field overrides it. While placing, the marker recolors automatically (red on invalid surface, amber when too far, gray while idle).

marker = { preset = 'bouncing_arrow' }
 
-- or fully custom
marker = {
    type = 2,                                 -- DrawMarker type
    scale = { x = 0.4, y = 0.4, z = 0.4 },
    color = { 34, 139, 230, 220 },            -- r, g, b, a
    offset = { x = 0.0, y = 0.0, z = 0.45 },  -- from ped head / model top; a bare number is z
    rotation = { x = 180.0, y = 0.0, z = 0.0 },
    bob = true,
    faceCamera = true,
}

| Returns | vector4 (x, y, z, w heading); a table with modelIndex/previewIndex when cycling; { multi = true, results = {...} } for multi place; false on cancel / already placing / invalid model |

local placement = exports.Newb_Bridge:PropPlacer('jim_g_beehive_prop_2', true, nil, 10.0)
 
if placement then
  TriggerServerEvent('myresource:placeHive', placement)
end

CreateZone(options) (client)

Signature: CreateZone(options: ZoneCreatorOptions): OxLibZoneTable?

Interactive zone creator with a NUI panel. Returns an ox_lib-compatible data table you can pass directly to lib.zones.poly, lib.zones.box, or lib.zones.sphere. Returns nil if the player cancels or validation fails.

ParamTypeDescription
optionsZoneCreatorOptionsZone type, name, and optional defaults

| Returns | OxLibPolyZone | OxLibBoxZone | OxLibSphereZone | nil |

Zone types

TypePlacementDescription
polyFreecamPolygon from placed points + thickness
boxOn footCenter + width/length/height + rotation
sphereOn footCenter + radius

name is required. Aliases: zoneid, zoneId.

Basic examples

-- Polygon (freecam)
local poly = exports.Newb_Bridge:CreateZone({
    type = 'poly',
    name = 'shop_floor',
    maxDistance = 50.0,
})
 
if poly then
    lib.zones.poly(poly)
end
-- Box (on foot)
local box = exports.Newb_Bridge:CreateZone({
    type = 'box',
    name = 'storage_room',
})
 
if box then
    lib.zones.box(box)
end
-- Sphere (on foot)
local sphere = exports.Newb_Bridge:CreateZone({
    type = 'sphere',
    name = 'atm_area',
})
 
if sphere then
    lib.zones.sphere(sphere)
end

Controls

Poly (freecam)

InputAction
LMBAdd point (requires laser hit)
RMBUndo last point
ScrollChange step size
Shift + ScrollAdjust thickness
XToggle edit / fly camera
WASD / Q / Left CtrlMove camera (fly mode; Q up, Left Ctrl down)
ShiftMove faster (fly mode)
FSwap freecam / on-foot
EConfirm
BackspaceCancel

On confirm, if the laser is hitting a surface, the current aim point is appended as the final polygon point.

Box / sphere (on foot)

InputAction
ScrollAdjust height (box) or radius (sphere)
Shift + ScrollChange step size
Ctrl + ScrollAdjust width (box)
Alt + ScrollAdjust length (box)
Left / Right arrowRotate heading (box)
BToggle free movement (optional lock with tablet anim)
FSwap freecam / on-foot
EConfirm (requires laser hit)
BackspaceCancel

Freecam events (anticheat)

Poly zones enter freecam before placement. Listen on the client to whitelist noclip-like behavior:

AddEventHandler('Newb_Bridge:client:zoneCreatorEnteredFreecam', function(data)
    -- data.name, data.type, data.anchor, data.maxDistance
end)
 
AddEventHandler('Newb_Bridge:client:zoneCreatorExitedFreecam', function(data)
    -- data.name, data.type
end)

See Events.

Validation errors

Error keyWhen
missing_namename not provided
invalid_typeType is not poly, box, or sphere
already_activeCreator already open
min_pointsPoly confirmed with fewer than 2 placed points
no_aimBox/sphere confirmed without laser hit, or poly point added without hit
outside_boundsBox/sphere center outside maxDistance from anchor

useGizmo(entity, options?) (client)

Signature: useGizmo(entity: number | GizmoCallOptions, options?: GizmoOptions): GizmoResult?

Opens the translation/rotation gizmo. Pass an entity handle to edit an existing entity, or a table with a model key to spawn-and-edit: the gizmo spawns a translucent frozen preview (ped/vehicle models get the matching entity type), edits it, and cleans it up on cancel.

OptionTypeDefaultDescription
deleteOnConfirmboolean?trueRemove the entity after confirm. false keeps a spawned preview as the placed entity (alpha/collision restored)
labelstring?entity archetypeShown in the gizmo panel header
outlineboolean?true for non-pedsDraw the green entity outline while editing. nil keeps the non-peds-only default
modelstring | numberSpawn-and-edit form only: model to spawn
coordsvector3 | vector42.5m in front of playerSpawn-and-edit: spawn position (w used as heading)
headingnumber?coords.wSpawn-and-edit: spawn heading
rotationvector3 | tableSpawn-and-edit: full rotation to seed the preview
pedboolean?autoSpawn-and-edit: force a ped. Vehicles spawn automatically when the model is a vehicle

| Returns | GizmoResult | nil when cancelled |

-- edit an existing entity
local result = exports.Newb_Bridge:useGizmo(prop, { deleteOnConfirm = false, label = 'Shop shelf' })
 
-- spawn-and-edit: no manual CreateObject / alpha / cleanup needed
local placed = exports.Newb_Bridge:useGizmo({
    model = 'prop_disp_cabinet_01',
    coords = vec4(215.4, -810.1, 30.7, 90.0),
})
if placed then
    print(placed.x, placed.y, placed.z, placed.heading)
end

Dialogue() (client)

Signature: Dialogue(): Dialogue

Returns the dialogue API table. Identical to bridge.dialogue. See Dialogue (client).

local Dialogue = exports.Newb_Bridge:Dialogue()
local id = Dialogue.RegisterMenu({ title = 'Shop', options = {} })
Dialogue.OpenMenu(id, ped)

VersionCheck(repoPath, resourceName) (server)

Signature: VersionCheck(repoPath: string, resourceName: string): nil

Checks GitHub resources.json for newer versions and prints patch notes to the server console.

ParamTypeDescription
repoPathstring"username/reponame" on GitHub
resourceNamestringResource folder name to look up in JSON

| Returns | nil |

AddEventHandler('onResourceStart', function(resource)
    if resource ~= GetCurrentResourceName() then return end
    exports.Newb_Bridge:VersionCheck('MrNewb/patchnotes', 'MrNewbBeeKeeping')
end)

The JSON repo should contain a resources.json entry keyed by resource name with versions and optional repo for release links.


Types

InteractionPayload

World interaction export payload (AddInteraction).

FieldType
modelnumber | string
coordsvector3
headingnumber
radiusnumber
optionsTargetOption[]?
snapToGroundboolean? props
invincibleboolean? peds, default true
componenttable? ped clothing
animtable? ped animation
scenariostring?
weaponnumber?
onSpawnfun(self: Interaction)?
onDespawnfun(self: Interaction)?
onEnterfun(self: Interaction): boolean? — return false to skip spawn
canSpawnfun(self: Interaction): boolean? — in-range spawn gate
bucketnumber | string? — only spawn when the local player is in this routing bucket (default 0)
rotationtable? — prop pitch/roll/yaw from gizmo placement
matrixtable? — full entity matrix from gizmo placement

InteractionSummary

Return shape from GetClosestInteraction() / GetNearbyInteractions().

FieldType
idstring
distancenumber
coordsvector3
headingnumber
radiusnumber
modelnumber | string
scenariostring?

GizmoResult

Return from useGizmo(entity).

FieldTypeDescription
handlenumberEntity handle (deleted when deleteOnConfirm is true)
positionvector3Final coords (from matrix at when present)
rotationvector3Raw GetEntityRotation
headingnumber
x, y, znumber
pitch, roll, yawnumberRounded rotation components
matrixtable?{ forward, right, up, at } from GetEntityMatrix

ZoneCreatorOptions

Options for CreateZone(options).

FieldTypeDefaultDescription
type'poly' | 'box' | 'sphere''poly'Zone shape. Alias: zoneType
namestringRequired. Zone name. Aliases: zoneid, zoneId
mode'freecam' | 'onfoot'freecam for poly, onfoot for box/sphereStarting mode (F swaps mid-session). Alias: startMode
maxDistancenumber50.0Poly freecam radius from anchor
anchorvector3player positionCenter / bounds origin
planeZnumberanchor or ped ZStarting Z for placement
stepnumber5 (poly/box), 1 (sphere)Step size index (1–11)
heightnumberpoly 8, box 2, sphere 1.05Poly thickness, box height, or sphere radius
thicknessnumberAlias for poly height
radiusnumberAlias for sphere height
widthnumber2.0Box width
lengthnumber2.0Box length
rotationnumberped headingBox rotation (degrees)
allowMovementbooleantrueAllow walking during on-foot placement
movementTogglebooleantrueShow B key movement lock toggle
elevatedStartbooleanfalseStart poly freecam elevated
elevatedHeightnumber18.0Elevated camera height
elevatedPitchnumber-35.0Elevated camera pitch
cameraHeightnumber1.65Eye-level freecam height
cameraPitchnumber0.0Eye-level freecam pitch
camEaseMsnumber1200Freecam enter/exit blend in ms; 0 for a hard cut

OxLibPolyZone

Return from CreateZone when type = 'poly'. Pass to lib.zones.poly(...).

FieldType
namestring
pointsvector3[]
thicknessnumber

OxLibBoxZone

Return from CreateZone when type = 'box'. Pass to lib.zones.box(...).

FieldType
namestring
coordsvector3
sizevector3
rotationnumber

OxLibSphereZone

Return from CreateZone when type = 'sphere'. Pass to lib.zones.sphere(...).

FieldType
namestring
coordsvector3
radiusnumber

Commands and keybinds (client)

Helpers also live on the bridge table after import.lua. Prefer exports.Newb_Bridge:… when you need rename-safe calls.

RegisterCommand(name, handler, restricted?)

exports.Newb_Bridge:RegisterCommand('myclientcmd', function(source, args, raw)
    print('ran', raw)
end, false)
 
-- Same via bridge:
bridge.registerCommand('myclientcmd', function(source, args, raw) end, false)

RegisterKeybind(name, description, defaultKey, handler)

exports.Newb_Bridge:RegisterKeybind('my_open', 'Open shop', 'F6', function()
    bridge.menu.openMenu({ id = 'shop', title = 'Shop', options = {} })
end)

GetBoundKey(commandName)

Returns the display label for a registered key mapping (or nil).

local key = exports.Newb_Bridge:GetBoundKey('my_open') or 'F6'
bridge.textui.showTextUI(('[%s] Open shop'):format(key))

Server admin commands still use bridge.framework.registerCommand (ACE + lib.addCommand).


DUI surfaces (client)

World-space DUI boards and prop texture replacement. Confirm with E, cancel with Backspace. Pixel sizes clamp to 256–2048; world size clamps to 0.25–12 m.

Content fields

content (alias dui on register/update) is normalized by normalizeContent:

FieldTypeDescription
srcstringURL / nui:// page. Alias: url
width / heightnumberTexture pixels (default 1024)
worldWidth / worldHeightnumber?On-world quad size in meters
aspectnumber?Height/width; default height / width
normalvector3?Surface normal; heading is used when omitted
cornerTL / cornerBRvector3?Placed-quad corners from CreateDuiPlacement
planeW / planeHnumber?Signed plane extents from placement
txd / txnstring?Optional texture dictionary / name on the content table

RegisterDuiSurface(key, data) requires data.coords. Pass content or dui.

Register / update / clear

exports.Newb_Bridge:RegisterDuiSurface('billboard_1', {
    coords = vec4(100.0, 200.0, 30.0, 90.0),
    content = {
        src = 'https://example.com/board.html',
        width = 1024,
        height = 512,
        worldWidth = 4.0,
        worldHeight = 2.0,
    },
})
 
exports.Newb_Bridge:UpdateDuiSurface('billboard_1', {
    content = { src = 'https://example.com/board-v2.html' },
})
 
exports.Newb_Bridge:UnregisterDuiSurface('billboard_1')
exports.Newb_Bridge:ClearDuiSurfaces()

Placement sessions

CreateDuiPlacement is two clicks: first corner, then opposite corner (aspect locked). Options: content (or content fields on the table), maxDistance (default 20), label. Returns { ok = true, coords, content } or { ok = false, cancelled = true } / { ok = false, reason = 'already_active' }.

CreateLaserTargetPlacement is a single aim: { maxDistance?, label? }{ ok = true, coords, normal } or the same failure tables.

-- Place a DUI board in the world (interactive)
local result = exports.Newb_Bridge:CreateDuiPlacement({
    content = { src = 'nui://my_resource/html/board.html', width = 512, height = 512 },
    maxDistance = 15.0,
    label = 'Place board',
})
if result.ok then
    exports.Newb_Bridge:RegisterDuiSurface('placed_board', {
        coords = result.coords,
        content = result.content,
    })
end
 
-- Laser target only (coords + normal, no DUI yet)
local aim = exports.Newb_Bridge:CreateLaserTargetPlacement({
    maxDistance = 20.0,
    label = 'Aim surface',
})

Queries

HasDuiPlacement(content) is a geometry check (cornerTL plus planeW or worldWidth) — not “is this key registered”. GetDuiBoardCenter / GetDuiTargetRadius use the same content table.

if exports.Newb_Bridge:HasDuiPlacement(content) then
    local center = exports.Newb_Bridge:GetDuiBoardCenter(content, coords)
    local radius = exports.Newb_Bridge:GetDuiTargetRadius(content)
end

Prop texture replacement

The second argument (entity) is unused — texture replacement is by txd/txn name.

exports.Newb_Bridge:ApplyPropDuiTexture('tv_screen', entity, {
    txd = 'prop_tv_flat_01',
    txn = 'script_rt_tvscreen',
    width = 512,
    height = 512,
}, {
    src = 'nui://my_resource/html/tv.html',
    width = 512,
    height = 512,
})
 
exports.Newb_Bridge:UpdatePropDuiTexture('tv_screen', { src = 'nui://my_resource/html/tv2.html' })
print(exports.Newb_Bridge:HasPropDuiTexture('tv_screen'))
exports.Newb_Bridge:ClearPropDuiTexture('tv_screen')

Task Groups and Task HUD

Full APIs live on dedicated module pages:

  • Task Groupsbridge.taskGroups, CreateTaskGroup, OpenTaskGroupMenu, …
  • Task HUDbridge.task / exports.Newb_Bridge:Task() (WIP)
-- Quick links
local groups = exports.Newb_Bridge:TaskGroups()
exports.Newb_Bridge:OpenTaskGroupMenu() -- client; server: OpenTaskGroupMenu(src)
 
local Task = exports.Newb_Bridge:Task()
Task.show({ title = 'Delivery', tasks = { { label = 'Drop off', current = 0, max = 1 } } })

Matching exports: CreateTaskGroup, AddToTaskGroup, RemoveFromTaskGroup, ClearTaskGroup, DeleteTaskGroup, IsInTaskGroup, IsTaskGroupLeader, GetTaskGroupLeader, SetTaskGroupLeader, GetTaskGroupMembers, GetPlayerTaskGroups, GetTaskGroupMemberCount, GetLocalTaskGroups (client), CloseTaskGroupMenu (client), IsTaskGroupMenuOpen (client), CompleteTask (client).