extends RefCounted
## ROAM — wander around home: the creatures' old behaviour, as a brain (creatures → bots,
## #114 phase 2; user 2026-10-13: the default for a placed creature).
##
## A creature body used to pick its own wander target on EVERY peer, from its own RNG,
## so two peers drew the same creature walking two different ways; a bot with no brain
## was therefore told to stand still. Here the room authority alone picks the next spot
## and publishes it (BotRegistry.move → world:bots:pos:), and every peer's body walks to
## that same spot with its own gait. No navmesh: it heads straight for a point within
## `radius` of home, as the creatures did.
## Injected by BotRegistry before _bot_init.
var bot_id: String = ""
const PARAMS := [
{"id": "radius", "label": "Roam radius", "kind": "float", "default": 12.0, "min": 1.0, "max": 128.0, "step": 1.0, "unit": "m"},
{"id": "pause", "label": "Seconds per stop", "kind": "float", "default": 5.0, "min": 0.5, "max": 60.0, "step": 0.5},
]
var radius: float = 12.0
var pause: float = 5.0
## The longest step to the next spot. A body TELEPORTS to a target further than its
## snap distance (GroundCrawler.SNAP_DISTANCE, 8 m — the spawn / late-joiner path), so
## a roam that jumped across its whole radius teleported instead of walking.
const STEP := 6.0
var home := Vector3.ZERO
var _from := Vector3.ZERO # the last spot published: where the body is headed
var _left := 0.0
var _rng := RandomNumberGenerator.new()
func _bot_init() -> void:
home = BotRegistry.start_position(bot_id) if BotRegistry != null else Vector3.ZERO
_from = home
_rng.randomize()
_next()
func _bot_tick(delta: float) -> void:
_left -= delta
if _left <= 0.0:
_next()
## The next spot — a walkable step (<= STEP) from the last, inside `radius` of home —
## and how long until the one after: pause .. 2 × pause, so a group placed together
## does not move in step.
func _next() -> void:
var a := _rng.randf() * TAU
var d := _rng.randf_range(0.3, 1.0) * minf(STEP, radius)
var p := _from + Vector3(cos(a) * d, 0.0, sin(a) * d)
if Vector2(p.x - home.x, p.z - home.z).length() > radius:
# Off the edge of its ground: step back toward home instead (never past it).
var back := Vector3(home.x - _from.x, 0.0, home.z - _from.z)
p = _from + back.limit_length(d)
p.y = home.y
_from = p
_left = _rng.randf_range(pause, pause * 2.0)
if BotRegistry != null and bot_id != "":
BotRegistry.move(bot_id, p)