yxc20098's picture
feat(engine+bench): raider bot โ€” worker-priority harasser (Rust ScriptedBehavior + bench registration)
de62afe
Raw
History Blame Contribute Delete
2.43 kB
"""YAML-referenceable scripted opponents (the enemy "bot generator").
A scenario pack can give the enemy a fixed, deterministic, map-agnostic
behaviour instead of only static actors + a stance:
enemy: {faction: soviet, bot_type: hunt} # or bot: hunt
The behaviour runs ENGINE-side (openra-sim `scripted_bot`), because the
Python boundary can neither see fogged enemy ids nor command the enemy
player. This module is the bench-facing surface: the catalogue of
behaviours + compile-time validation so an unknown name fails fast
(mirrors mapgen). `bot_type` is the field that survives the training
schema's serialization; `bot` is an accepted alias.
Behaviours (all derive targets from live world state โ€” no map coords):
hunt โ€” each unit attacks the nearest agent unit; pursues.
rusher โ€” relentless concentrated charge at the agent's mass; fast.
patrol โ€” units oscillate around their own spawn; engage intruders.
turtle โ€” hold the spawn position; return fire only.
guard โ€” leashed defender: holds its post; lunges at a
foe only within an aggro radius and snaps back
past a leash (a decoy can bait it off post).
raider โ€” worker-priority harasser: each unit attacks the
nearest agent harvester; falls back to nearest
combat actor only when no harvesters are alive
(the faithful SC2-style worker harass).
"""
from __future__ import annotations
from typing import Any
BEHAVIORS: frozenset[str] = frozenset(
{"hunt", "rusher", "patrol", "turtle", "guard", "raider"}
)
def read_enemy_bot(enemy: Any) -> str | None:
"""Extract the bot behaviour from an `enemy:` block (dict), honouring
`bot_type` (survives serialization) or the `bot` alias. Empty/missing
โ‡’ None (stance-only reactive enemy, the default)."""
if not isinstance(enemy, dict):
return None
raw = enemy.get("bot_type") or enemy.get("bot")
if raw is None:
return None
s = str(raw).strip().lower()
return s or None
def validate_enemy_bot(enemy: Any) -> str | None:
"""Compile-time gate: a declared enemy bot must name a known
behaviour, else the pack fails at load (not mid-eval)."""
b = read_enemy_bot(enemy)
if b is not None and b not in BEHAVIORS:
raise ValueError(
f"unknown enemy bot {b!r}; known behaviours: "
f"{sorted(BEHAVIORS)}"
)
return b