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(
is_aggressive=False,
respawns=True,
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 CombatStatsComponent
# Make NPC killable
npc.add(HealthComponent(max_hp=100, current_hp=100))
# Give NPC combat abilities
npc.add(CombatStatsComponent(
level=5,
strength=14,
dexterity=12,
attack_bonus=3,
armor_class=15,
))
# Give NPC items
npc.add(InventoryComponent(capacity=20))
Behavior Components¶
BehaviorConfig¶
The BehaviorConfig component defines how an NPC acts:
from maid_classic_rpg.models.npc.behavior import NPCState, PatrolState
# Runtime state (created by system)
npc_state = NPCState(
entity_id=npc.id,
patrol_state=PatrolState.IDLE,
in_combat=False,
territory_center=guard_room.id,
territory_radius=3,
)
Aggression Levels¶
| Level | Behavior |
|---|---|
| Passive | Never attacks, flees if attacked |
| Neutral | Attacks only when attacked first |
| Defensive | Attacks players who attack allies |
| Aggressive | Attacks players on sight |
| Hostile to Faction | Attacks specific faction members |
Configure in NPCComponent:
npc.add(NPCComponent(
is_aggressive=True,
aggro_radius=3, # Rooms
faction="orcs",
hostile_factions=["humans", "elves"],
))
Threat and Memory¶
NPCs remember who attacked them:
# In behavior system
state.threat_memory[attacker_id] = threat_level
state.last_seen[attacker_id] = current_time
# Get highest threat target
target_id = state.get_highest_threat_target()
Dialogue Systems¶
MAID supports two dialogue approaches:
- Static Dialogue - Predefined trigger/response pairs
- AI Dialogue - Dynamic LLM-generated responses
Static Dialogue¶
For simple NPCs with predictable conversations:
from maid_classic_rpg.models.npc.behavior import NPCDialogue, DialogueLine
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¶
Triggers match player input:
# 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.systems.npc import BehaviorSystem
# Define patrol route (list of room IDs)
patrol_route = [
gate_room.id,
wall_room_1.id,
tower_room.id,
wall_room_2.id,
# Returns to gate_room
]
# Configure NPC
npc_state.patrol_route = patrol_route
npc_state.patrol_state = PatrolState.PATROLLING
npc_state.patrol_speed = 30.0 # Seconds between moves
Wandering Behavior¶
NPCs move randomly within a territory:
# Configure territory
npc_state.territory_center = tavern_room.id
npc_state.territory_radius = 2 # Rooms from center
# Enable wandering
npc.add(NPCComponent(
wander=True,
wander_chance=0.1, # 10% chance each tick
))
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 maid_classic_rpg.models.economy.shop import ShopInventory, ShopItem
# 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(
is_aggressive=False,
shop_enabled=True,
))
# Configure shop inventory
shop = ShopInventory(
owner_id=shopkeeper.id,
name="Marcus's General Goods",
buy_multiplier=0.5, # Pays 50% of item value
sell_multiplier=1.2, # Sells at 120% of value
items=[
ShopItem(item_template="torch", quantity=-1, price=1), # Unlimited
ShopItem(item_template="rope", quantity=10, price=5),
ShopItem(item_template="health_potion", quantity=5, price=50),
],
restock_interval=3600.0, # 1 hour
)
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:
def calculate_price(base_price: int, player: Entity, shop: ShopInventory) -> int:
price = base_price * shop.sell_multiplier
# Reputation discount
rep = player.get(ReputationComponent)
if rep and rep.get_faction_rep("merchants") > 50:
price *= 0.9 # 10% discount
# Supply/demand
if shop.get_stock(item_type) < 3:
price *= 1.2 # Low stock premium
return int(price)
Quest Givers¶
Quest NPC Structure¶
from maid_classic_rpg.models.quest import Quest, QuestObjective, QuestReward
# 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(
quest_giver=True,
available_quests=["goblin_camp", "missing_patrol"],
))
Defining Quests¶
quest = Quest(
id="goblin_camp",
name="Clear the Goblin Camp",
description="A goblin camp has been spotted near the village. Eliminate the threat.",
giver_id=captain.id,
level_requirement=5,
objectives=[
QuestObjective(
id="kill_goblins",
type="kill",
target="goblin",
count=10,
description="Kill 10 goblins",
),
QuestObjective(
id="kill_chief",
type="kill",
target="goblin_chief",
count=1,
description="Kill the Goblin Chief",
),
],
rewards=QuestReward(
gold=500,
xp=1000,
items=["steel_sword"],
reputation={"town_guard": 100},
),
dialogue={
"offer": "Those goblins have been raiding our supplies. We need someone to clear them out.",
"progress": "How goes the hunt? Have you found their camp?",
"complete": "You've done the town a great service. Here's your reward.",
},
)
Quest Dialogue Integration¶
dialogue = NPCDialogue(
npc_id=captain.id,
greeting="Ah, an adventurer. Perhaps you can help.",
lines=[
# Quest not started
DialogueLine(
trigger="quest|help|job",
response=quest.dialogue["offer"],
conditions={"quest_status:goblin_camp": "not_started"},
actions=["offer_quest:goblin_camp"],
),
# Quest in progress
DialogueLine(
trigger="quest|report",
response=quest.dialogue["progress"],
conditions={"quest_status:goblin_camp": "in_progress"},
),
# Quest ready to turn in
DialogueLine(
trigger="quest|report|done",
response=quest.dialogue["complete"],
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.
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:
# Bartender (AI-powered)
bartender = create_npc(
name="Old Tom",
room=tavern,
dialogue=create_bartender_dialogue("The Rusty Bucket"),
)
# Shopkeeper (scripted)
shopkeeper = create_npc(
name="Merchant Mary",
room=market,
shop=ShopInventory(
items=[
ShopItem("torch", -1, 1),
ShopItem("rope", 10, 5),
],
),
dialogue=NPCDialogue(
greeting="Welcome to my shop!",
lines=[
DialogueLine("buy", "Just say 'list' to see my wares."),
],
),
)
# Guard (behavioral, scripted dialogue)
guard = create_npc(
name="Gate Guard",
room=town_gate,
patrol_route=[gate, wall_1, tower, wall_2],
dialogue=create_guard_dialogue("the main gate"),
)
# Quest Giver (AI-powered)
mayor = create_npc(
name="Mayor Thornwood",
room=town_hall,
dialogue=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"],
),
quests=["goblin_threat", "missing_villagers"],
)
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