from __future__ import annotations from typing import Callable from engine.character import Character from engine.defs import DefRegistry from engine.world import World from resources.logic.combat import DiceRoller from resources.logic.turrets import TURRET_BEHAVIOR_ID, turret_ai_behavior # Seconds between wander steps, randomized so a room full of NPCs doesn't lock-step. WANDER_INTERVAL = (2.0, 5.0) _WANDER_DIRECTIONS: list[tuple[int, int]] = [(-1, 0), (1, 0), (0, -1), (0, 1)] def _wander(character: Character, world: World, registry: DefRegistry, now: float, rng: DiceRoller) -> None: """The simplest useful example behavior: every so often, take one random step in a cardinal direction (a no-op if that step is blocked - see World.try_move). This exists to prove the ai-slot framework actually reaches into the world, not as a real patrol/pathing AI - most NPCs will want a purpose-built behavior of their own registered alongside it. """ assert character.ai is not None if now < character.ai.state.get("next_move_at", 0.0): return dx, dy = rng.choice(_WANDER_DIRECTIONS) world.try_move(character, dx, dy, 0) character.ai.state["next_move_at"] = now + rng.uniform(*WANDER_INTERVAL) # behavior_id (see EntityDef.ai_behavior_id / AIController.behavior_id) -> tick function. Add a # new NPC behavior by writing its function and registering it here - run_ai_tick needs no # changes to pick it up. AI_BEHAVIORS: dict[str, Callable[[Character, World, DefRegistry, float, DiceRoller], None]] = { "wander": _wander, TURRET_BEHAVIOR_ID: turret_ai_behavior, } def run_ai_tick(character: Character, world: World, registry: DefRegistry, now: float, rng: DiceRoller) -> None: """Ticks whichever behavior `character.ai.behavior_id` names, if any. A no-op for a player-controlled or otherwise inert Character (`ai is None`) and for an unrecognized behavior_id, so a bad/typo'd def can never crash the tick loop. Meant to be called once per Character per elapsed game tick (see main.py's PLAYING per-tick block). """ if character.ai is None: return behavior = AI_BEHAVIORS.get(character.ai.behavior_id) if behavior is None: return behavior(character, world, registry, now, rng)