Mi2dRPGamEng/resources/logic/ai.py

51 lines
2.2 KiB
Python

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)