Add NPC/mob system documentation
This commit is contained in:
parent
50a1d356a8
commit
db8b395257
1 changed files with 193 additions and 0 deletions
193
docs/how/npc-mobs.rst
Normal file
193
docs/how/npc-mobs.rst
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
================
|
||||||
|
npc/mob system
|
||||||
|
================
|
||||||
|
|
||||||
|
the npc/mob system handles non-player characters: enemies, allies, townsfolk. mobs can wander, patrol, converse, flee, and fight. they follow schedules and have behavior states that transition based on game events.
|
||||||
|
|
||||||
|
entity model
|
||||||
|
============
|
||||||
|
|
||||||
|
all characters (players and mobs) inherit from ``Entity`` in ``entity.py``. this gives them:
|
||||||
|
|
||||||
|
- ``pl`` (power level) - combat effectiveness
|
||||||
|
- ``stamina`` and ``max_stamina`` - resource for moves
|
||||||
|
- ``posture`` - computed @property with priority order: "unconscious", "fighting", "flying", "sleeping", "resting", "standing"
|
||||||
|
- ``x``, ``y``, ``zone`` - location in the world
|
||||||
|
|
||||||
|
the ``Mob`` subclass adds:
|
||||||
|
|
||||||
|
- ``description`` - what you see when you look at them
|
||||||
|
- ``alive`` - whether they're dead
|
||||||
|
- ``moves`` - list of combat move names they can use
|
||||||
|
- ``next_action_at`` - throttle for AI decisions
|
||||||
|
- ``home_x_min``, ``home_x_max``, ``home_y_min``, ``home_y_max`` - wander bounds
|
||||||
|
- ``behavior_state`` - current state: "idle", "patrol", "converse", "flee", "working"
|
||||||
|
- ``behavior_data`` - dict with state-specific data (waypoints, threat coords, etc)
|
||||||
|
- ``npc_name`` - key into dialogue tree registry
|
||||||
|
- ``schedule`` - ``NpcSchedule`` instance with hourly behavior changes
|
||||||
|
|
||||||
|
mob templates
|
||||||
|
=============
|
||||||
|
|
||||||
|
templates live in ``content/mobs/`` as TOML files. ``MobTemplate`` in ``mobs.py`` defines the schema::
|
||||||
|
|
||||||
|
name = "goblin"
|
||||||
|
description = "a snarling goblin with a crude club"
|
||||||
|
pl = 50.0
|
||||||
|
stamina = 40.0
|
||||||
|
max_stamina = 40.0
|
||||||
|
moves = ["punch left", "punch right", "sweep"]
|
||||||
|
|
||||||
|
[[loot]]
|
||||||
|
name = "crude club"
|
||||||
|
chance = 0.8
|
||||||
|
description = "a crude wooden club"
|
||||||
|
|
||||||
|
``spawn_mob(template, x, y, zone, home_region)`` creates a ``Mob`` from a template and registers it. ``despawn_mob(mob)`` marks it dead and removes it from the registry. ``get_nearby_mob(name, x, y, zone, radius)`` finds mobs by name within range (toroidal-aware).
|
||||||
|
|
||||||
|
behavior states
|
||||||
|
===============
|
||||||
|
|
||||||
|
the behavior system in ``npc_behavior.py`` is a state machine. ``process_behavior(mob)`` dispatches to handlers based on ``mob.behavior_state``:
|
||||||
|
|
||||||
|
- **idle** - default state, wanders toward home region center
|
||||||
|
- **patrol** - moves toward waypoints in ``behavior_data["waypoints"]``
|
||||||
|
- **converse** - locked in place, talking to a player
|
||||||
|
- **flee** - runs away from threat at ``behavior_data["flee_from"]``
|
||||||
|
- **working** - schedule-driven state (librarian at desk, blacksmith at forge)
|
||||||
|
|
||||||
|
transitions happen when:
|
||||||
|
|
||||||
|
- player starts conversation → "converse"
|
||||||
|
- schedule hour changes → state from active schedule entry
|
||||||
|
- conversation ends → restores ``previous_state`` from conversation data
|
||||||
|
- flee timeout expires → return to previous state
|
||||||
|
|
||||||
|
helper functions calculate movement direction:
|
||||||
|
|
||||||
|
- ``get_patrol_direction(mob)`` - toward next waypoint (toroidal)
|
||||||
|
- ``get_flee_direction(mob)`` - away from threat (toroidal)
|
||||||
|
|
||||||
|
schedules
|
||||||
|
=========
|
||||||
|
|
||||||
|
scheduled NPCs have ``NpcSchedule`` in ``npc_schedule.py``. a schedule is a list of ``ScheduleEntry``::
|
||||||
|
|
||||||
|
[[schedule]]
|
||||||
|
hour = 7
|
||||||
|
state = "working"
|
||||||
|
location = [10, 20, "town"]
|
||||||
|
|
||||||
|
[[schedule]]
|
||||||
|
hour = 21
|
||||||
|
state = "idle"
|
||||||
|
|
||||||
|
``get_active_entry(hour)`` returns the most recent entry at or before the given hour. ``process_schedules(game_time)`` is called from the game loop when the hour changes. it:
|
||||||
|
|
||||||
|
- finds all mobs with schedules
|
||||||
|
- skips dead mobs and mobs in conversation
|
||||||
|
- checks if the active entry changed
|
||||||
|
- teleports mob to new location if specified
|
||||||
|
- transitions to new state
|
||||||
|
- clears behavior_data if state changed
|
||||||
|
|
||||||
|
mob ai
|
||||||
|
======
|
||||||
|
|
||||||
|
combat ai
|
||||||
|
---------
|
||||||
|
|
||||||
|
``process_mobs()`` in ``mob_ai.py`` runs every tick (10/sec). for mobs in combat:
|
||||||
|
|
||||||
|
- **defender with incoming attack** - 40% chance of correct counter, 60% random affordable defense
|
||||||
|
- **attacker outside defend window** - random affordable attack
|
||||||
|
|
||||||
|
the ai respects throttles (``next_action_at``) and stamina costs. it uses ``get_affordable_moves()`` to filter by stamina.
|
||||||
|
|
||||||
|
movement ai
|
||||||
|
-----------
|
||||||
|
|
||||||
|
``process_mob_movement()`` runs every tick with a 3-second cooldown per mob. behavior-based movement:
|
||||||
|
|
||||||
|
- **patrol** - toward next waypoint, cycles through list
|
||||||
|
- **flee** - away from threat coordinates
|
||||||
|
- **idle** - toward home region center
|
||||||
|
- **working** - no movement
|
||||||
|
|
||||||
|
movement checks passability via ``world.is_passable(x, y, zone)``. broadcasts departure/arrival to nearby players. uses toroidal distance for direction calculation.
|
||||||
|
|
||||||
|
dialogue and conversation
|
||||||
|
=========================
|
||||||
|
|
||||||
|
``DialogueTree`` in ``dialogue.py`` has ``DialogueNode`` entries::
|
||||||
|
|
||||||
|
[dialogue.librarian.greeting]
|
||||||
|
text = "welcome to the library"
|
||||||
|
|
||||||
|
[[dialogue.librarian.greeting.choices]]
|
||||||
|
text = "what books do you have?"
|
||||||
|
next_node = "books"
|
||||||
|
|
||||||
|
``DialogueChoice`` has optional ``condition`` (python expression) and ``action`` (function name).
|
||||||
|
|
||||||
|
``ConversationState`` in ``conversation.py`` tracks:
|
||||||
|
|
||||||
|
- ``tree`` - the dialogue tree
|
||||||
|
- ``current_node`` - id of current node
|
||||||
|
- ``npc`` - the mob being talked to
|
||||||
|
- ``previous_state`` - mob's state before conversation
|
||||||
|
|
||||||
|
``start_conversation(player, npc)`` transitions the mob to "converse" state. ``end_conversation(player)`` restores the mob's previous state. active conversations are stored in a dict keyed by player name.
|
||||||
|
|
||||||
|
commands
|
||||||
|
========
|
||||||
|
|
||||||
|
talk and reply
|
||||||
|
--------------
|
||||||
|
|
||||||
|
``cmd_talk`` in ``commands/talk.py``:
|
||||||
|
|
||||||
|
- finds nearby NPC by name
|
||||||
|
- starts conversation
|
||||||
|
- shows greeting text and choices
|
||||||
|
|
||||||
|
``cmd_reply``:
|
||||||
|
|
||||||
|
- takes choice number as argument
|
||||||
|
- advances to next node
|
||||||
|
- ends conversation if no next node
|
||||||
|
|
||||||
|
spawn
|
||||||
|
-----
|
||||||
|
|
||||||
|
``cmd_spawn`` in ``commands/spawn.py``:
|
||||||
|
|
||||||
|
- takes template name
|
||||||
|
- creates mob at player location
|
||||||
|
- sets home region to 5-tile radius
|
||||||
|
|
||||||
|
game loop integration
|
||||||
|
=====================
|
||||||
|
|
||||||
|
the ``game_loop()`` function in ``server.py`` calls:
|
||||||
|
|
||||||
|
- ``process_mobs()`` - every tick (10/sec)
|
||||||
|
- ``process_mob_movement()`` - every tick (10/sec)
|
||||||
|
- ``process_schedules(game_time)`` - when hour changes
|
||||||
|
|
||||||
|
this keeps mob AI responsive while preventing spam. the throttles (``next_action_at`` for combat, 3s cooldown for movement) ensure mobs don't act too frequently.
|
||||||
|
|
||||||
|
code
|
||||||
|
====
|
||||||
|
|
||||||
|
- ``src/mudlib/entity.py`` - Entity and Mob classes
|
||||||
|
- ``src/mudlib/mobs.py`` - MobTemplate, spawn/despawn registry
|
||||||
|
- ``src/mudlib/npc_behavior.py`` - behavior state machine
|
||||||
|
- ``src/mudlib/npc_schedule.py`` - NpcSchedule and ScheduleEntry
|
||||||
|
- ``src/mudlib/mob_ai.py`` - combat and movement AI
|
||||||
|
- ``src/mudlib/dialogue.py`` - DialogueTree, DialogueNode, DialogueChoice
|
||||||
|
- ``src/mudlib/conversation.py`` - ConversationState, start/end conversation
|
||||||
|
- ``src/mudlib/commands/talk.py`` - talk and reply commands
|
||||||
|
- ``src/mudlib/commands/spawn.py`` - spawn command
|
||||||
|
- ``content/mobs/`` - mob template TOML files
|
||||||
|
- ``content/dialogue/`` - dialogue tree TOML files
|
||||||
Loading…
Reference in a new issue