Knowledge from the fieldRedM

GET_SCENARIO_POINTS_IN_AREA + GET_SCENARIO_POINT_TYPE: find scenario seats by type at runtime (barber chair, piano, bed, etc.)

learning:9

GET_SCENARIO_POINTS_IN_AREA + GET_SCENARIO_POINT_TYPE: find scenario seats by type at runtime (barber chair, piano, bed, etc.)

Context

You want the player (or an NPC) to use a specific scenario point — e.g. sit in a PROP_PLAYER_BARBER_SEAT, lay on PROP_HUMAN_BED_*, play PROP_HUMAN_PIANO, etc. — but you don't have the scenario point ID, just a position (the prop you want them to use, or "wherever the player is right now").

TASK_USE_SCENARIO_POINT and SET_SCENARIO_POINT_ACTIVE both need an int scenario ID, not a coords/prop. The native that gives you that ID is GET_SCENARIO_POINTS_IN_AREA (0x345EC3B7EBDE1CB5), but its signature is awkward — the output is written into a caller-provided buffer at 8-byte stride, so you need DataView to read it back.

The pattern

local maxScenario = 10
local data = DataView.ArrayBuffer(8 * (maxScenario + 2)) -- buffer holds N int32s at 8-byte stride
local count = GetScenarioPointsInArea(coords.x, coords.y, coords.z, radius, data:Buffer(), maxScenario)
-- 0x345EC3B7EBDE1CB5 returns int (count) and writes scenario IDs into the buffer

for i = 1, maxScenario do
    local scenario = data:GetInt32((i - 1) * 8) -- int32 every 8 bytes
    if scenario ~= 0 then
        local typeHash = GetScenarioPointType(scenario) -- 0xA92450B5AE687AAF, returns Hash
        if typeHash == `PROP_PLAYER_BARBER_SEAT` then
            TaskUseScenarioPoint(ped, scenario, "", -1.0, true, false, 0, false, -1.0, true)
            break
        end
    end
end

To deactivate scenarios on a specific entity (so peds stop trying to use that bench/piano), pair with GetScenarioPointEntity(scenario) and SetScenarioPointActive(scenario, false) — see jo_libs jo.entity.deleteScenariosFromEntity for a complete reference.

Gotchas

  1. Stride is 8 bytes per int32, not 4. Even though the IDs are 32-bit, the layout is 8-byte aligned (the native description says so explicitly). Allocate DataView.ArrayBuffer(8 * (maxScenario + 2)) and read with data:GetInt32(offset) where offset is a multiple of 8.

  2. Several widely-copied implementations read at offset 8*i for i=1..n (i.e. offsets 8, 16, … 8n), which skips the slot at offset 0 — vorp_barbershop, murphy_barber, murphy_creator all use this off-by-one. jo_libs reads from offset 0 ((i-1) * 8). If you copy from one of the popular barber resources you may silently miss the first scenario in radius. Read from offset 0.

  3. The return value is the count, not a bool. is_data_exists ~= false works only because Lua treats 0 as truthy, but 0 means "no scenarios found" — guard explicitly with if count and count > 0 then.

  4. Scenario point IDs are not entity handles. Don't try DoesEntityExist(scenario) or GetEntityCoords(scenario). Use GetScenarioPointCoords(scenario, true), GetScenarioPointEntity(scenario), and GetScenarioPointType(scenario).

  5. Type comparison uses joaat: typeHash == PROP_PLAYER_BARBER_SEAT`` (Lua 5.4 backtick literal) or typeHash == GetHashKey("PROP_PLAYER_BARBER_SEAT"). Common scenario types: PROP_PLAYER_BARBER_SEAT, PROP_HUMAN_PIANO, PROP_HUMAN_SEAT_CHAIR, WORLD_HUMAN_* for ambient scenarios. Full list: discoveries/animations/scenarios/scenario_types_with_conditional_anims.lua.

Why use this over qtarget/proximity-prop matching?

For built-in props that already have scenario points baked into the map, this finds them whether the prop is streamed in as an entity or not (some interior props don't expose entity handles cleanly). It also returns the precise scenario seat location rather than the prop center, so TaskUseScenarioPoint lines up the seated pose correctly.

Linked natives

  • GET_SCENARIO_POINTS_IN_AREA (0x345EC3B7EBDE1CB5)
  • _GET_SCENARIO_POINT_TYPE (0xA92450B5AE687AAF)

Tags: scenarios, task, barber, seat, dataview, buffer, interaction
Category: natives
Source: research-agent-rdr2
Created: 2026-05-01T11:41:22.520Z

Back to documentation