> ## Documentation Index
> Fetch the complete documentation index at: https://lationscripts.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Interact - Points

> Static interaction points anchored to world coordinates

A point is the simplest anchor: a stack of options pinned to a world coordinate. Register one with `addPoint`, keep the returned id if you ever want to remove it, and the layer handles the rest - the [reveal stages](/docs/interact/how-it-works), focus, and input all come for free. Everything a resource registers is cleaned up automatically when that resource stops.

<Note>
  Points are indexed in a spatial grid, and the frame loop only ever looks at the player's neighborhood - so thousands of registered points cost nothing at idle. Register your whole map up front; there's no need to create and destroy points as players move around.
</Note>

## Quick Start

```lua theme={null}
local atm = exports.lation_interact:addPoint({
    coords = vector3(241.9, -862.4, 29.7),
    label = 'ATM',
    icon = 'credit-card',
    options = {
        {
            label = 'Withdraw Cash',
            icon = 'money-bill',
            onSelect = function(data)
                -- open your banking UI
            end
        },
        {
            label = 'Deposit Cash',
            icon = 'piggy-bank',
            serverEvent = 'bank:deposit'
        }
    }
})
```

Walking up to it: a grain of light marks the point from `8.0m`, the label grows out of the grain at `4.5m`, and within `2.4m` the stack is live. The defaults come from `Config.Stages` and can be overridden per point with `stages` - see [How It Works](/docs/interact/how-it-works) for the full attention model.

## Functions

<AccordionGroup>
  <Accordion title="addPoint" icon="location-dot">
    Register a static interaction point.

    ```lua theme={null}
    local id = exports.lation_interact:addPoint(data)
    ```

    **Parameters:**

    * `data` (table) - Point configuration (see [Point Options](#point-options))

    **Returns:**

    * `id` (number) - The point's anchor id, for `remove` - or `nil` if the data was invalid (the reason is printed to the client console)
  </Accordion>

  <Accordion title="remove" icon="trash">
    Remove an anchor by id. `removePoint` is an alias - both accept the id returned by any `add*` export, whatever its kind.

    ```lua theme={null}
    exports.lation_interact:remove(id)

    -- alias, identical behavior
    exports.lation_interact:removePoint(id)
    ```

    **Parameters:**

    * `id` (number) - The id returned at registration

    <Note>
      Manual removal is only needed for points that stop making sense mid-session. Everything your resource registered is removed automatically when it stops or restarts.
    </Note>
  </Accordion>
</AccordionGroup>

## Configuration

### Point Options

<ParamField path="coords" type="vector3" required>
  World position of the point. A `vector4` or a plain `{ x, y, z }` table (JSON, database rows) is accepted too
</ParamField>

<ParamField path="label" type="string">
  Header line of the stack

  <Note>
    Optional - without one (or when it just repeats the first option's label) the stack renders headerless: the header line *is* the first option. Ideal for single-action points.
  </Note>
</ParamField>

<ParamField path="icon" type="string">
  FontAwesome solid icon name for the anchor (e.g. `'credit-card'`) - omit for the default dot
</ParamField>

<ParamField path="options" type="table" required>
  Array of one or more option tables (see [Option Fields](#option-fields))
</ParamField>

<ParamField path="stages" type="table">
  Per-point overrides of the reveal distances, in meters: `{ grain = 8.0, name = 4.5, reach = 2.4 }`

  Any key you omit keeps its `Config.Stages` default
</ParamField>

<ParamField path="expand" default="smart" type="string">
  How the stack opens once the player is in reach

  Available options: `'smart'`, `'auto'`, `'manual'`

  `'smart'` unfolds single-option points on their own and holds multi-option points as a pill until the confirm key opens the list; `'auto'` always unfolds; `'manual'` always waits for the confirm key. The default comes from `Config.ExpandMode`
</ParamField>

<ParamField path="reveal" default="ambient" type="string">
  What renders before the point is focused

  Available options: `'ambient'`, `'focus'`

  `'ambient'` marks the point with a grain of light from the grain stage onward; `'focus'` renders nothing until attention lands on it. Points default to `'ambient'` (`Config.Reveal.point`)
</ParamField>

<ParamField path="debug" type="boolean" default="false">
  Draw the point's reach sphere in-world, at any distance - useful while placing coordinates
</ParamField>

<ParamField path="passive" type="boolean">
  Hidden until the interact control is held - see [passive mode](/docs/interact/how-it-works#passive-mode). Defaults to `Config.Passive`, or `true` when your resource is listed in `Config.PassiveResources`
</ParamField>

<ParamField path="inVehicle" type="boolean">
  Whether the point stays live while the player sits in a vehicle. Unset, it follows `Config.InVehicle.points` (`true`)
</ParamField>

### Option Fields

Each entry in `options` describes one row of the stack:

<ParamField path="label" type="string" required>
  Row label
</ParamField>

<ParamField path="icon" type="string">
  FontAwesome solid icon name (e.g. `'money-bill'`)
</ParamField>

<ParamField path="key" type="string">
  Keyboard key this option binds to, by name (`'E'`, `'T'`, `'5'`, `'F2'`, `'SPACE'`, ...) - pressing it while the stack is open activates the option directly

  <Note>
    Omit to leave the option unbound: the confirm key (`E` by default) activates it while it's the highlighted row, and the cursor (hold Left Alt and click - then hover, click) reaches it anywhere in the list. Binding a key name that doesn't exist fails the registration with a console error.
  </Note>
</ParamField>

<ParamField path="hold" type="number">
  Milliseconds the key must be held - a ring fills while held and the option fires when it completes; releasing early cancels. Omit or `0` for instant activation
</ParamField>

<ParamField path="keepOpen" type="boolean" default="false">
  Keep the list open after selecting this option (repeatable actions) - `'manual'` lists fold back to the pill after a selection by default
</ParamField>

<ParamField path="groups" type="string | string[] | table">
  Job/gang gate: `'police'`, `{ 'police', 'sheriff' }` (any of), or `{ police = 2 }` (any of, at that minimum grade)

  Matches whatever your framework reports as the player's groups - jobs and gangs alike. Custom setups can adapt the check in `bridge/client.lua`
</ParamField>

<ParamField path="items" type="string | string[] | table">
  Inventory gate: `'lockpick'`, `{ 'lockpick', 'screwdriver' }` (all of), or `{ lockpick = 2 }` (required counts)

  Supported inventories are detected automatically; custom setups can adapt the check in `bridge/client.lua`
</ParamField>

<ParamField path="canInteract" type="function">
  Predicate deciding whether the row is currently visible - return `true` to show it:

  ```lua theme={null}
  canInteract = function(data)
      -- data: { id = number, entity = nil, args = any, distance = number? }
      return not IsPedInAnyVehicle(PlayerPedId(), false)
  end
  ```

  Evaluated off the frame path on a dedicated thread every `50ms` while the point is focused (and about 5x/s while merely nearby) - a slow predicate only delays its own rows, never the element or input. It is *not* re-run at activation
</ParamField>

<ParamField path="onSelect" type="function">
  Handler called when the option activates, with a single payload table:

  ```lua theme={null}
  onSelect = function(data)
      -- data: { id = pointId, index = optionIndex, args = any, coords = vector3 }
  end
  ```

  Handlers dispatch from their own thread, so it's safe to `Wait`, open UIs, or await server callbacks inside
</ParamField>

<ParamField path="event" type="string">
  Client event triggered on activation with the same payload table as `onSelect`
</ParamField>

<ParamField path="serverEvent" type="string">
  Server event triggered on activation with `{ id, index, args, netId, coords }` (`netId` is `nil` for points)
</ParamField>

<ParamField path="args" type="any">
  Passed through to `canInteract` and every handler
</ParamField>

<Note>
  `onSelect`, `event`, and `serverEvent` are not exclusive - an option may set any combination, and all of them fire.
</Note>

<Warning>
  The `state` statebag gate applies to entity anchors only. A point has no statebag, so a `state`-gated option on a point prints a warning at registration and stays hidden.
</Warning>

## Gated Options and Visibility

Rows gated by `groups`, `items`, or `canInteract` simply don't render for players who fail the gate - the stack shows only what the player can actually use. If *every* option on a point is gated away, the point disappears entirely: no grain, no focus, nothing to see.

```lua theme={null}
exports.lation_interact:addPoint({
    coords = vector3(452.1, -980.0, 30.7),
    label = 'Evidence Locker',
    icon = 'box-archive',
    options = {
        {
            label = 'Open Locker',
            icon = 'key',
            groups = { police = 2 },
            serverEvent = 'evidence:open'
        },
        {
            label = 'Pick Lock',
            icon = 'screwdriver-wrench',
            items = 'lockpick',
            hold = 1500,
            serverEvent = 'evidence:pick'
        }
    }
})
```

A grade-2 officer sees the first row, a burglar carrying a lockpick sees the second, and everyone else walks past a locker that never lights up.
