Creating NPCs Guide¶
This guide covers everything you need to know about creating Non-Player Characters (NPCs) in MAID, from simple shopkeepers to complex AI-powered characters.
Table of Contents¶
- Overview
- NPC Entity Structure
- Behavior Components
- Dialogue Systems
- Patrol and Wandering
- Shopkeepers and Traders
- Quest Givers
- AI-Powered NPCs
- Best Practices
Overview¶
NPCs in MAID are entities with specialized components that define their appearance, behavior, and interactions. You can create NPCs that:
- Stand guard at specific locations
- Patrol between waypoints
- Wander randomly within a territory
- Trade items with players
- Give quests and track progress
- Engage in combat when threatened
- Have conversations using AI or scripted dialogue
NPC Complexity Levels¶
| Level | Description | Use Case |
|---|---|---|
| Static | No behavior, just description | Background atmosphere |
| Scripted | Predefined responses and actions | Vendors, quest givers |
| Behavioral | Patrol, wander, react to events | Guards, monsters |
| AI-Powered | Dynamic conversations using LLMs | Important characters |
NPC Entity Structure¶
Basic NPC Creation¶
In-game command:
Core NPC Components¶
Every NPC needs these components:
from maid_stdlib.components import (
DescriptionComponent,
PositionComponent,
NPCComponent,
)
# Create NPC entity
npc = world.create_entity()
# Required: Description
npc.add(DescriptionComponent(
name="Town Guard",
short_desc="A stern guard in chainmail",
long_desc="A stern-looking guard stands at attention, his hand resting on his sword hilt.",
keywords=["guard", "soldier", "watchman"],
))
# Required: Position
npc.add(PositionComponent(room_id=town_gate_room.id))
# Required: NPC marker
npc.add(NPCComponent(
behavior_type="passive", # passive, hostile, friendly, merchant, etc.
respawn_time=300.0, # 5 minutes
))
Optional Components¶
Add components based on NPC functionality:
from maid_stdlib.components import HealthComponent, InventoryComponent
from maid_classic_rpg.components import CharacterStatsComponent, CharacterInfoComponent
# Make NPC killable
npc.add(HealthComponent(current=100, maximum=100))
# Give NPC D&D-style ability scores
npc.add(CharacterStatsComponent(
strength=14,
dexterity=12,
constitution=13,
))
# Class, race, and level live on CharacterInfoComponent
npc.add(CharacterInfoComponent(
name="Town Guard",
race="human",
character_class="warrior",
level=5,
))
# Give NPC items
npc.add(InventoryComponent(capacity=20))
Behavior Components¶
BehaviorConfig¶
BehaviorConfig describes how an NPC acts. Register it with the
BehaviorSystem, which creates and tracks the NPC's runtime NPCState:
from maid_classic_rpg.models.entities.monster import BehaviorConfig, BehaviorType
behavior = BehaviorConfig(
behavior_type=BehaviorType.GUARDIAN,
aggro_range=3, # Rooms to detect enemies
home_room_id=guard_room.id,
)
# register_npc returns the runtime NPCState the system will process
npc_state = behavior_system.register_npc(
entity_id=npc.id,
behavior=behavior,
territory_center=guard_room.id,
)
Aggression Levels¶
| BehaviorType | Behavior |
|---|---|
PASSIVE |
Won't attack unless attacked |
NEUTRAL |
Won't attack unless provoked |
AGGRESSIVE |
Attacks on sight |
TERRITORIAL |
Attacks if a player lingers |
COWARDLY |
Flees when damaged |
GUARDIAN |
Protects an area or entity |
PACK |
Calls for help from nearby allies |
AMBUSH |
Waits hidden, then attacks |
Configure aggression via BehaviorConfig.behavior_type:
from maid_classic_rpg.models.entities.monster import BehaviorConfig, BehaviorType
behavior = BehaviorConfig(
behavior_type=BehaviorType.AGGRESSIVE, # attacks on sight
aggro_range=3, # rooms to detect enemies
)
behavior_system.register_npc(entity_id=npc.id, behavior=behavior)
Threat and Memory¶
NPCs remember who attacked them. Always update threat through the behavior
system's helpers — they store entity IDs as strings, which is what
get_highest_threat_target() expects. Inserting a raw UUID key directly into
threat_memory will crash get_highest_threat_target() (it calls UUID(key)
on the stored key):
# Correct: go through the behavior system (stores str(entity_id) internally)
behavior_system.update_threat(npc_id=npc.id, entity_id=attacker.id, threat=10)
behavior_system.record_sighting(npc_id=npc.id, entity_id=attacker.id)
# Get highest threat target (returns a UUID | None)
state = behavior_system.get_npc_state(npc.id)
target_id = state.get_highest_threat_target() if state else None
When an NPC takes damage, BehaviorSystem also calls update_threat
automatically from its DamageDealtEvent handler, so pack/call-for-help
behavior is populated without manual wiring.
Dialogue Systems¶
The built-in live dialogue system (NPCDialogueSystem) is driven entirely by
the DialogueComponent you attach to an NPC entity — see
AI-Powered NPCs below. It handles player talk/ask/greet
commands, LLM responses, and scripted fallbacks.
The NPCDialogue / DialogueLine models described next are data definitions
only — they are not consumed by any built-in system. Use them if you write your
own scripted-dialogue handler in a content pack; otherwise attach a
DialogueComponent.
Static Dialogue (model definitions — not wired to the live system)¶
Not consumed by the built-in dialogue system
NPCDialogue and DialogueLine are plain models (not ECS components, so they
cannot be attached to an entity with entity.add(...)). The built-in
NPCDialogueSystem reads only DialogueComponent. The example below shows how
to describe scripted trigger/response lines; wiring them to gameplay requires
a custom system in your content pack.
For simple NPCs with predictable conversations, you can model the lines as data:
from maid_classic_rpg.models.npc.behavior import NPCDialogue, DialogueLine
# A data description of scripted lines — consumed only by a custom handler.
dialogue = NPCDialogue(
npc_id=guard.id,
greeting="Halt! State your business.",
farewell="Move along, citizen.",
default_response="I don't have time for idle chatter.",
lines=[
DialogueLine(
trigger="curfew",
response="Curfew is at sundown. No exceptions.",
),
DialogueLine(
trigger="trouble",
response="There have been rumors of goblins near the forest. Stay alert.",
),
DialogueLine(
trigger="captain",
response="The Captain is in the barracks. He doesn't see just anyone.",
conditions={"reputation": "friendly"},
),
DialogueLine(
trigger="bribe",
response="*looks around* I might forget I saw you... for a price.",
conditions={"reputation": "neutral"},
actions=["start_quest:bribe_guard"],
),
],
)
Dialogue Triggers¶
DialogueLine.trigger is a keyword/pattern string, and conditions/actions
are string maps/lists your custom handler interprets:
# Exact match
DialogueLine(trigger="hello", response="Greetings!")
# Keyword in message
DialogueLine(trigger="help", response="What kind of help do you need?")
# Multiple triggers
DialogueLine(trigger="yes|agree|okay", response="Very well.")
Conditional Dialogue¶
Show different responses based on game state:
DialogueLine(
trigger="quest",
response="I have a task for you...",
conditions={
"quest_status:main_quest": "not_started",
"player_level": ">=5",
},
)
DialogueLine(
trigger="quest",
response="You're already on a mission. Focus!",
conditions={
"quest_status:main_quest": "in_progress",
},
)
DialogueLine(
trigger="quest",
response="You've done well. The town thanks you.",
conditions={
"quest_status:main_quest": "completed",
},
)
Dialogue Actions¶
Trigger game actions from dialogue:
DialogueLine(
trigger="accept",
response="Excellent! Here's what you need to know...",
actions=[
"start_quest:rescue_princess",
"give_item:old_map",
"set_flag:talked_to_king",
"teleport:throne_room",
],
)
Patrol and Wandering¶
Patrol Behavior¶
NPCs follow a predefined route:
from maid_classic_rpg.models.entities.monster import BehaviorConfig, BehaviorType
# Define the patrol route as an ordered list of room IDs
patrol_rooms = [
gate_room.id,
wall_room_1.id,
tower_room.id,
wall_room_2.id,
]
# Patrol routes live on BehaviorConfig.patrol_rooms
behavior = BehaviorConfig(
behavior_type=BehaviorType.GUARDIAN,
home_room_id=gate_room.id,
patrol_rooms=patrol_rooms,
)
behavior_system.register_npc(entity_id=npc.id, behavior=behavior)
Wandering Behavior¶
NPCs move randomly within a territory:
from maid_classic_rpg.models.entities.monster import BehaviorConfig, BehaviorType
# Wandering is driven by BehaviorConfig.wander_chance around a home room
behavior = BehaviorConfig(
behavior_type=BehaviorType.NEUTRAL,
home_room_id=tavern_room.id,
wander_chance=0.1, # 10% chance to wander each processing cycle
)
behavior_system.register_npc(
entity_id=npc.id,
behavior=behavior,
territory_center=tavern_room.id,
)
Returning Home¶
When combat ends, NPCs return to their origin:
# After combat ends
if npc_state.patrol_state == PatrolState.ENGAGED:
npc_state.patrol_state = PatrolState.RETURNING
# System will move NPC back to territory_center
Shopkeepers and Traders¶
Creating a Shop NPC¶
from uuid import uuid4
from maid_classic_rpg.models.economy.shop import Shop, ShopInventoryEntry, ShopType
# Create shopkeeper
shopkeeper = world.create_entity()
shopkeeper.add(DescriptionComponent(
name="Marcus the Merchant",
short_desc="A portly merchant with a friendly smile",
keywords=["marcus", "merchant", "shopkeeper"],
))
shopkeeper.add(NPCComponent(
behavior_type="merchant",
is_merchant=True,
))
# Item template IDs come from your loaded item templates
torch_id, rope_id, potion_id = uuid4(), uuid4(), uuid4()
# Build the shop and register it with the ShopSystem
shop = Shop(
name="Marcus's General Goods",
owner_id=shopkeeper.id,
room_id=market_room.id,
shop_type=ShopType.GENERAL,
buy_multiplier=1.2, # Player pays 120% of value when buying
sell_multiplier=0.5, # Player receives 50% of value when selling
inventory=[
ShopInventoryEntry(item_template_id=torch_id, base_price=1, quantity=-1), # Unlimited
ShopInventoryEntry(item_template_id=rope_id, base_price=5, quantity=10),
ShopInventoryEntry(item_template_id=potion_id, base_price=50, quantity=5),
],
)
# ShopSystem is registered by the maid-classic-rpg content pack
shop_system.manager.register_shop(shop)
Shop Commands¶
Players interact with shops using:
list - Show shop inventory
buy <item> [count] - Purchase items
sell <item> [count] - Sell items to shop
appraise <item> - Check item's sell value
Dynamic Pricing¶
Adjust prices based on supply, demand, or reputation:
from maid_classic_rpg.models.social.faction import CharacterReputation
# Shops price items from a base value, the buyer's charisma modifier, and their
# reputation with the shop's faction. Both helpers live on the Shop model.
reputation = CharacterReputation(faction_id=merchants_faction_id, value=60)
buy_price = shop.calculate_buy_price(
base_value=50,
charisma_modifier=2, # from the buyer's CharacterStatsComponent
reputation=reputation.value, # 0-100
)
sell_price = shop.calculate_sell_price(
base_value=50,
charisma_modifier=2,
reputation=reputation.value,
)
Quest Givers¶
Quest NPC Structure¶
from maid_stdlib.models.quest import (
Quest,
QuestObjective,
QuestReward,
QuestPrerequisite,
ObjectiveType,
)
# Create quest giver
quest_giver = world.create_entity()
quest_giver.add(DescriptionComponent(
name="Captain Aldric",
short_desc="A grizzled veteran with a worried expression",
keywords=["captain", "aldric", "veteran"],
))
quest_giver.add(NPCComponent(
behavior_type="friendly",
is_quest_giver=True,
))
Defining Quests¶
quest = Quest(
name="Clear the Goblin Camp",
quest_id="goblin_camp",
title="Clear the Goblin Camp",
description="A goblin camp has been spotted near the village. Eliminate the threat.",
giver_npc_id=quest_giver.id,
prerequisites=QuestPrerequisite(min_level=5),
objectives=[
QuestObjective(
objective_id="kill_goblins",
objective_type=ObjectiveType.KILL,
description="Kill 10 goblins",
target_name="goblin",
target_count=10,
),
QuestObjective(
objective_id="kill_chief",
objective_type=ObjectiveType.KILL,
description="Kill the Goblin Chief",
target_name="goblin_chief",
target_count=1,
),
],
rewards=QuestReward(
experience=1000,
gold=500,
item_template_ids=[steel_sword_template_id], # reward item template UUIDs
reputation_changes={"town_guard": 100},
),
accept_dialogue="Those goblins have been raiding our supplies. We need someone to clear them out.",
progress_dialogue="How goes the hunt? Have you found their camp?",
complete_dialogue="You've done the town a great service. Here's your reward.",
)
Quest Dialogue Integration¶
The Quest model already carries the strings the quest system uses at each
stage — accept_dialogue, progress_dialogue, and complete_dialogue (set in
Defining Quests above). These are the supported, wired-up
fields.
If you want the quest giver to volunteer those lines in response to player
keywords, you can describe them with NPCDialogue/DialogueLine — but, as
noted under Static Dialogue above, those models are not consumed by the
built-in dialogue system and require a custom handler:
# Illustrative data only — requires a custom scripted-dialogue handler.
dialogue = NPCDialogue(
npc_id=quest_giver.id,
greeting="Ah, an adventurer. Perhaps you can help.",
lines=[
# Quest not started
DialogueLine(
trigger="quest|help|job",
response=quest.accept_dialogue,
conditions={"quest_status:goblin_camp": "not_started"},
actions=["offer_quest:goblin_camp"],
),
# Quest in progress
DialogueLine(
trigger="quest|report",
response=quest.progress_dialogue,
conditions={"quest_status:goblin_camp": "in_progress"},
),
# Quest ready to turn in
DialogueLine(
trigger="quest|report|done",
response=quest.complete_dialogue,
conditions={"quest_status:goblin_camp": "ready_to_complete"},
actions=["complete_quest:goblin_camp"],
),
],
)
AI-Powered NPCs¶
For important characters who need dynamic, contextual conversations, use AI dialogue.
Basic AI NPC Setup¶
from maid_stdlib.components import DialogueComponent
# Create NPC entity (same as before)
bartender = world.create_entity()
bartender.add(DescriptionComponent(
name="Old Grimsby",
short_desc="A grizzled bartender with knowing eyes",
keywords=["bartender", "grimsby", "barkeep"],
))
bartender.add(NPCComponent())
# Add AI dialogue
bartender.add(DialogueComponent(
ai_enabled=True,
personality=(
"A gruff but kind-hearted bartender who's seen it all. "
"Wise about the world but never preachy. Has a soft spot "
"for down-on-their-luck adventurers."
),
speaking_style=(
"Short sentences. Occasional grunts. Uses 'aye' and 'nay'. "
"Wipes glasses while talking. Speaks in a low voice."
),
npc_role="bartender at The Rusty Tankard tavern",
knowledge_domains=[
"local gossip",
"drinks and brewing",
"travelers' tales",
"regular customers",
],
secret_knowledge=[
"knows about the smuggler's tunnel beneath the tavern",
"witnessed the murder last winter",
],
will_discuss=["drinks", "gossip", "weather", "room rates"],
wont_discuss=["his past", "the tunnel", "the murder"],
greeting="*looks up from polishing a glass* What'll it be?",
farewell="*nods* Take care out there.",
max_response_tokens=100,
temperature=0.7,
))
AI Configuration¶
Control AI behavior with these settings:
| Setting | Range | Description |
|---|---|---|
temperature |
0.0-1.0 | Higher = more creative/varied |
max_response_tokens |
50-500 | Response length |
cooldown_seconds |
1.0+ | Time between responses |
Temperature guidelines:
| Value | Character Type |
|---|---|
| 0.3-0.4 | Guards, officials (formal, predictable) |
| 0.5-0.6 | Merchants, craftsmen (consistent) |
| 0.7-0.8 | Bartenders, adventurers (balanced) |
| 0.9-1.0 | Mystics, tricksters (unpredictable) |
Using Factory Functions¶
For common NPC types, use the provided factories:
from maid_classic_rpg.data.npcs.dialogue_configs import (
create_bartender_dialogue,
create_guard_dialogue,
create_merchant_dialogue,
create_quest_giver_dialogue,
)
# Quick bartender setup
bartender.add(create_bartender_dialogue(
tavern_name="The Golden Griffin",
temperature=0.8,
))
# Quick guard setup
guard.add(create_guard_dialogue(
location="the eastern watchtower",
))
Player Commands for AI NPCs¶
talk <npc> <message> - Talk to NPC
ask <npc> about <topic> - Ask about something
greet <npc> - Send greeting (aliases: hello, hi)
conversations - List active conversations
endconversation <npc> - End conversation (alias: bye)
See the AI-Powered NPC Dialogue Guide for complete documentation.
NPC Autonomy¶
Added in v3.1.
Autonomy components give NPCs independent goals, needs, and daily schedules so they behave proactively rather than only reacting to player input.
NeedsComponent¶
NeedsComponent tracks NPC needs across four categories:
| NeedCategory | Examples |
|---|---|
SURVIVAL |
Hunger, rest, safety |
ECONOMIC |
Income, stock, trade |
PURPOSE |
Duty, craft, patrol |
COMFORT |
Socializing, shelter, warmth |
Each need has a 0.0–1.0 satisfaction value that decays over time, driving the NPC to seek fulfillment.
GoalsComponent¶
GoalsComponent holds a prioritized list of goals. Each goal has a GoalCategory and a GoalPredicate protocol that determines when the goal is satisfied:
from maid_stdlib.models.npc.autonomy import GoalsComponent, Goal, GoalCategory
npc.add(GoalsComponent(active_goals=[
Goal(category=GoalCategory.ACQUIRE, description="Restock shop inventory", priority=0.8),
Goal(category=GoalCategory.DUTY, description="Patrol the market square", priority=0.5),
]))
ScheduleComponent¶
ScheduleComponent defines a daily routine as a list of ScheduleBlock entries:
from maid_stdlib.models.npc.autonomy import ScheduleComponent, ScheduleBlock, ActivityType
npc.add(ScheduleComponent(blocks=[
ScheduleBlock(start_hour=6, end_hour=8, activity=ActivityType.EAT, location="tavern", priority=1.0),
ScheduleBlock(start_hour=8, end_hour=17, activity=ActivityType.TRADE, location="market", priority=1.0),
ScheduleBlock(start_hour=17, end_hour=20, activity=ActivityType.SOCIALIZE, location="tavern", priority=1.0),
ScheduleBlock(start_hour=20, end_hour=6, activity=ActivityType.SLEEP, location="home", priority=1.0),
]))
NPC Archetypes¶
Added in v3.1.
Archetypes are YAML-based NPC templates that define personality weights, need weights and decay rates, a default daily schedule, and innate/forbidden goals. They support single inheritance via the parent field.
The maid-classic-rpg pack loads its archetypes from the bundled
maid_classic_rpg/data/npcs/archetypes.yaml. The file has a top-level
archetypes: list, and each entry is keyed by archetype_id:
archetypes:
- archetype_id: shopkeeper
display_name: Shopkeeper
need_weights:
survival: 0.3
economic: 0.9
purpose: 0.7
comfort: 0.5
default_schedule:
- start_hour: 8
end_hour: 18
activity: work
location: workplace
priority: 0.9
- start_hour: 18
end_hour: 22
activity: socialize
location: tavern
priority: 0.7
- start_hour: 22
end_hour: 8
activity: sleep
location: home
priority: 1.0
innate_goals:
- category: duty
description: Keep the shop running.
priority: 0.7
personality:
diligence: 0.8
greed: 0.6
sociability: 0.7
- archetype_id: blacksmith
display_name: Blacksmith
parent: shopkeeper
need_weights:
purpose: 0.9
When an NPC is created with an archetype, the loader merges inherited fields (from parent) with the archetype's own overrides.
Bark System¶
Added in v3.1.
Barks are short ambient lines that NPCs emit without making LLM calls. They add flavor and make the world feel alive at zero API cost.
BarkTemplate¶
Each BarkTemplate has a text string and gating fields: the need that
triggers it (trigger_need), a need_threshold, an allowed mood_range, and
a per-line cooldown_minutes. The YAML is a top-level map whose keys are bark
pools:
# maid_classic_rpg/data/npcs/barks.yaml
default:
- text: "Fine goods for sale! Best prices in town!"
trigger_need: economic
need_threshold: 0.5
mood_range: [0.3, 1.0]
cooldown_minutes: 8
- text: "*rearranges wares on the counter*"
trigger_need: purpose
need_threshold: 0.4
mood_range: [0.2, 1.0]
cooldown_minutes: 12
Bark definitions are loaded by the maid-classic-rpg pack from the bundled
maid_classic_rpg/data/npcs/barks.yaml. For each NPC, BarkSystem selects from
the pool named str(NPCComponent.template_id) plus the default pool,
filtering by the NPC's needs, mood, and each line's cooldown.
Archetype-named bark pools do not match spawned NPCs
BarkSystem looks up bark pools by str(NPCComponent.template_id) — the
spawned NPC's MonsterTemplate UUID, not a human-readable archetype name.
A pool keyed by a name such as merchant: therefore never matches a
spawner-created NPC (whose template_id is a UUID). Put barks you want every
NPC to use under the default: key, as shown above. (Autonomy components map
templates to archetypes via MonsterTemplate.metadata["archetype_id"], but the
bark system does not use that mapping.)
Spawner Integration¶
Added in v3.1.
When NPCs are spawned via the spawner system, autonomy components
(NeedsComponent, GoalsComponent, ScheduleComponent) are attached
automatically based on the archetype inferred from the spawned template — no
manual wiring is needed.
Spawning itself is driven by registered spawn points and templates rather than
a direct spawn call. You register them with the SpawnerSystem, which spawns
and respawns entities during tick processing:
from maid_classic_rpg.models.world.spawn import SpawnPoint
# Register the monster template and a spawn point that references it
spawner_system.register_template(guard_captain_template) # a MonsterTemplate
spawner_system.register_spawn_point(SpawnPoint(
room_id=barracks.id,
template_id=guard_captain_template.id,
max_count=1,
))
# On its next processing cycle the SpawnerSystem spawns the NPC and, if the
# template maps to an archetype, attaches its needs, goals, and schedule.
Best Practices¶
NPC Design Tips¶
- Give NPCs purpose: Every NPC should have a reason to exist (vendor, quest, lore)
- Consistent personality: Maintain character across all dialogue options
- Memorable quirks: Add unique speech patterns, catchphrases, or behaviors
- Appropriate knowledge: NPCs should know about their role, not everything
Performance Considerations¶
| NPC Count | Recommendations |
|---|---|
| < 50 | AI dialogue for all |
| 50-200 | AI for important NPCs, scripted for others |
| 200+ | Mostly scripted, AI for key characters |
AI Cost Management¶
- Set reasonable
max_response_tokens(100-150 for most NPCs) - Use
cooldown_secondsto prevent spam - Consider per-player daily token budgets
- Use local Ollama for development
Testing NPCs¶
# Test dialogue
talk <npc> Hello!
talk <npc> Tell me about yourself.
ask <npc> about local news
# Test shop
list
buy torch 5
sell sword
# Test quest
talk <npc> Do you have a quest?
# Complete objectives
talk <npc> I'm done.
Example: Complete Village NPCs¶
Here's a complete example of setting up a village with various NPC types:
from uuid import uuid4
from maid_stdlib.components import (
DescriptionComponent, PositionComponent, NPCComponent, DialogueComponent,
)
from maid_classic_rpg.models.economy.shop import Shop, ShopInventoryEntry
from maid_classic_rpg.models.entities.monster import BehaviorConfig, BehaviorType
from maid_classic_rpg.data.npcs.dialogue_configs import (
create_bartender_dialogue, create_guard_dialogue,
)
# Bartender (AI-powered, using a real dialogue factory)
bartender = world.create_entity()
bartender.add(DescriptionComponent(name="Old Tom", keywords=["tom", "bartender"]))
bartender.add(PositionComponent(room_id=tavern.id))
bartender.add(NPCComponent(behavior_type="friendly"))
bartender.add(create_bartender_dialogue(tavern_name="The Rusty Bucket"))
# Shopkeeper (scripted shop registered with the ShopSystem)
shopkeeper = world.create_entity()
shopkeeper.add(DescriptionComponent(name="Merchant Mary", keywords=["mary", "merchant"]))
shopkeeper.add(PositionComponent(room_id=market.id))
shopkeeper.add(NPCComponent(behavior_type="merchant", is_merchant=True))
shop_system.manager.register_shop(Shop(
name="Mary's Wares",
owner_id=shopkeeper.id,
room_id=market.id,
inventory=[
ShopInventoryEntry(item_template_id=uuid4(), base_price=1, quantity=-1),
ShopInventoryEntry(item_template_id=uuid4(), base_price=5, quantity=10),
],
))
# Guard (behavioral patrol + scripted dialogue)
guard = world.create_entity()
guard.add(DescriptionComponent(name="Gate Guard", keywords=["guard"]))
guard.add(PositionComponent(room_id=town_gate.id))
guard.add(NPCComponent(behavior_type="passive"))
behavior_system.register_npc(
entity_id=guard.id,
behavior=BehaviorConfig(
behavior_type=BehaviorType.GUARDIAN,
home_room_id=town_gate.id,
patrol_rooms=[town_gate.id, wall_1.id, tower.id, wall_2.id],
),
)
guard.add(create_guard_dialogue(location="the main gate"))
# Quest Giver (AI-powered)
mayor = world.create_entity()
mayor.add(DescriptionComponent(name="Mayor Thornwood", keywords=["mayor", "thornwood"]))
mayor.add(PositionComponent(room_id=town_hall.id))
mayor.add(NPCComponent(behavior_type="friendly", is_quest_giver=True))
mayor.add(DialogueComponent(
ai_enabled=True,
personality="A worried but determined leader facing a crisis.",
npc_role="mayor of the village",
knowledge_domains=["village history", "current crisis", "politics"],
))
Related Documentation¶
- AI-Powered NPC Dialogue Guide - Complete AI dialogue documentation
- AI Configuration Reference - AI provider settings
- Combat Systems Guide - NPC combat configuration
- Building Worlds Guide - Placing NPCs in the world