Map, markers & zones
World-anchored POIs
The open77_worldui facade: a ground marker and its interaction prompt created and destroyed as one owned handle, the definition reference, and the invisible-ring trap it exists to avoid.
On this page7 sections
open77_worldui combines a native ground marker and an interaction prompt under one resource-owned handle. Creation and cleanup cover both elements.
It is mode-agnostic: nothing in its API names a role, a match, a team, or
any other gamemode-shaped concept, and both official consumers
(resources/gamemodes/pursuit and resources/gamemodes/race) call it through the same three
functions below with no special-casing on either side.
Source: open77_worldui.
Add the dependency
resource "hideseek"
version "1.0.0"
dependency "open77_worldui >=0.1.0"
client_script "client/main.lua"
world.markers is not required by the caller -- open77_worldui holds
that permission itself and is the presentation boundary precisely so a
gamemode resource does not need it (see
docs/writing-a-gamemode.md section 1, point 3,
"least privilege"). A caller that also wants open77_interactions directly,
bypassing this facade, needs its own dependency and permission for that --
this service does not re-export open77_interactions' surface.
Create a POI
local handle
CreateThread(function()
local promise, callError = Open77.exports.call("open77_worldui", "create", {
id = "start_line",
position = { x = -1460.2, y = 99.9, z = 24.8 },
radius = 2.5,
style = "objective",
maxDistance = 90.0,
label = "Join the race",
description = "Hold to line up at the start.",
key = "E",
holdSeconds = 1.0,
color = "#00E5FF",
event = "hideseek:startLineSelected",
})
assert(promise, callError)
local result, awaitError = promise:await()
assert(result and result.ok, awaitError or (result and result.error))
handle = result.handle
end)
AddEventHandler("hideseek:startLineSelected", function()
TriggerServerEvent("hideseek:joinIntent")
end)
label is optional. Omitting it creates a marker with no interaction
prompt -- a pure ground/waypoint ring with nothing to press E on. This is
the right shape for a checkpoint or a waypoint a player only needs to see,
not act on; resources/gamemodes/race's course markers use exactly this form.
Definition reference
| Field | Meaning |
|---|---|
id |
Owner-local identifier, 1-64 characters. Required. |
position |
Static { x, y, z } ground point. Required. |
style |
One of the four fixed native presets: interaction, objective, spawn, danger. Defaults to objective. |
shape |
ring (default) or cylinder. |
radius |
Marker radius, clamped to 0.1-50.0 m; defaults to 1.5. |
maxDistance |
Marker visibility distance, clamped to 1.0-500.0 m; defaults to 80.0. |
groundOffset |
Metres the marker is lifted off position.z, 0.0-2.0; defaults to 0.06. A ring flattened to the ground plane at exactly floor height is co-planar with the floor and renders nothing visible even though the handle and the native registry both report success -- see "A trap this service exists to avoid" below. |
label, description |
Prompt copy. Presence of label is what creates the prompt half at all. |
key, holdSeconds, icon, color |
Prompt affordance, passed straight through to open77_interactions. |
marker, animated, focusRadius, requireLookAt, promptDistance, labelHeight |
Prompt presentation fields; see Contextual interactions for their meaning and ranges. |
event |
Local event fired when the prompt's choice is selected. Required if label is set. |
Radius and maxDistance are clamped here rather than left to fail against
the native marker's own tighter bounds, so a caller gets a POI instead of
an opaque native refusal.
The prompt half's markerScale and markerNearScale are not
configurable through this facade -- they are fixed at 1.0 and 1.6
regardless of what definition carries. A caller that needs different
prompt-marker scaling has to create the interaction directly through
open77_interactions instead of through this service.
Update, list, remove
local function call(name, ...)
local promise, reason = Open77.exports.call("open77_worldui", name, ...)
assert(promise, reason)
return promise:await()
end
local owned = call("list").pois -- { { handle, id, position }, ... }
assert(call("remove", handle).ok)
There is no update export. Remove and recreate under the same id to
change a POI's geometry or copy -- the same convention as open77_zones.
dump() is a diagnostic, not part of the steady-state API: it reads the
native marker registry through this resource's world.markers permission
(a caller without that permission cannot see the registry at all) and logs
one line per marker, including the native rendered flag. Use it when a
POI is not visually appearing to separate "the Lua handle exists" from "the
REDengine effect is actually alive" -- the two failure modes look identical
from Lua otherwise, and rendered=true has been measured to mislead on its
own (see docs/gamemode-pursuit-plan.md section 7d).
Ownership and cleanup
Ownership comes from GetInvokingResource(), never from an argument.
remove and list are scoped to the caller; a handle belonging to another
resource is refused with not_owner. A caller's POIs (both the marker and
the prompt half) are removed automatically when its resource generation
stops or reloads. A limit of 128 POIs applies across every resource
combined.
Surviving open77_interactions restarting underneath you
open77_interactions declares reload_policy "reconnect"
(Contextual interactions), so any change to the
session's resource set restarts it, and on restart it drops every
registered entry. A POI's marker half survives (it belongs to
open77_worldui's own generation), but the prompt half silently vanishes,
leaving a visible ring with nothing to interact with.
open77_worldui watches open77_interactions' generation once a second
and re-registers every prompt it is holding a promptSpec for when that
generation changes. A caller does not need to do anything for this -- it is
part of why the facade exists rather than every gamemode composing the two
services itself.
A trap this service exists to avoid
A marker's ring mesh is flattened to ~0.04 m. Placed at exactly
position.z with no lift, it sits co-planar with the ground mesh: the
native entity spawns, the effect scales, the registry reports
rendered = true -- and nothing is visible, because the ring is inside the
floor geometry. groundOffset (default 0.06 m) exists specifically to keep
a caller from rediscovering this. If a POI is invisible despite dump()
reporting rendered = true, suspect a groundOffset of 0 before
anything else.
// For agents & LLMsthis page as Markdownllms.txtllms-full.txt
