NPCPlugin¶
Manages NPC state, movement, pathfinding, dialog progression, and interactions.
Location¶
- Implementation: src/pedre/plugins/npc/plugin.py
- Base class: src/pedre/plugins/npc/base.py
- Events: src/pedre/plugins/npc/events.py
- Actions: src/pedre/plugins/npc/actions.py
- Conditions: src/pedre/plugins/npc/conditions.py
- Sprites: src/pedre/plugins/npc/sprites.py
Configuration¶
The NPCPlugin uses the following settings from pedre.conf.settings:
Movement and Interaction Settings¶
NPC_INTERACTION_DISTANCE- Maximum distance in pixels for player to interact with NPCs (default: 50)NPC_WAYPOINT_THRESHOLD- Distance in pixels to consider an NPC has reached a waypoint (default: 5)NPC_MOVEMENT_SPEED- Movement speed in pixels per second (default: 80.0)NPC_INTERACTION_KEY- Key to interact with NPCs (default: "SPACE")
These can be overridden in your project's settings.py:
# Custom NPC settings
NPC_INTERACTION_DISTANCE = 60
NPC_WAYPOINT_THRESHOLD = 8
NPC_MOVEMENT_SPEED = 100.0
NPC_INTERACTION_KEY = "E"
Public API¶
NPC Registration¶
register_npc¶
register_npc(sprite: arcade.Sprite, name: str) -> None
Register an NPC sprite for management.
Parameters:
sprite- The NPC sprite (typically AnimatedNPC)name- Unique identifier for the NPC
Example:
# Create and register an NPC
npc_sprite = AnimatedNPC(
sprite_sheet="characters/merchant.png",
center_x=320,
center_y=240
)
npc_plugin.register_npc(npc_sprite, "merchant")
Notes:
- NPCs must be registered before they can be interacted with
- Name should be unique within the scene
- Usually called automatically by
load_npcs_from_objects()
Dialog Management¶
load_dialogs_from_json¶
load_dialogs_from_json(json_path: Path | str) -> bool
Load NPC dialog configurations from a JSON file or directory.
Parameters:
json_path- Path to JSON file or directory containing dialog files
Returns:
Trueif dialogs loaded successfully,Falseotherwise
Example:
# Load from single file
npc_plugin.load_dialogs_from_json("assets/dialogs/village_dialogs.json")
# Load all dialogs from directory
npc_plugin.load_dialogs_from_json("assets/dialogs/")
Notes:
- Files are named
{scene}_dialogs.json(e.g.,casa_dialogs.json) - Scene name is extracted from filename
- Loading a directory processes all
.jsonfiles
get_dialog¶
get_dialog(npc_name: str, dialog_level: int, scene: str = "default") -> tuple[NPCDialogConfig | None, list[dict[str, Any]] | None]
Get dialog for an NPC at a specific conversation level in a scene.
Parameters:
npc_name- The NPC namedialog_level- Current conversation levelscene- Scene name (defaults to "default")
Returns:
- Tuple of
(dialog_config, on_condition_fail_actions): dialog_config: NPCDialogConfig if conditions met, None if no dialog foundon_condition_fail_actions: List of actions if conditions failed, None otherwise
Example:
current_scene = context.scene_plugin.get_current_scene()
dialog_data = npc_plugin.get_dialog("merchant", 0, current_scene)
if dialog_data:
dialog_config, on_fail = dialog_data
if dialog_config:
dialog_plugin.show_dialog("Merchant", dialog_config.text)
Notes:
- Checks dialog conditions using ConditionRegistry
- Falls back to "default" scene if scene-specific dialog not found
- Returns
on_condition_failactions when conditions aren't met
advance_dialog¶
advance_dialog(npc_name: str) -> int
Advance the dialog level for an NPC by 1.
Parameters:
npc_name- The NPC name
Returns:
- The new dialog level
Example:
# After completing a conversation
new_level = npc_plugin.advance_dialog("merchant")
print(f"Merchant now at dialog level {new_level}")
Notes:
- Increments dialog_level by 1
- Dialog level tracks conversation progression
- Used to show different dialog on subsequent interactions
Movement and Pathfinding¶
move_npc_to_tile¶
move_npc_to_tile(npc_name: str, tile_x: int | float, tile_y: int | float) -> None
Start moving an NPC to a target tile position using A* pathfinding.
Parameters:
npc_name- Name of the NPC to movetile_x- Target tile x coordinatetile_y- Target tile y coordinate
Example:
Notes:
- Uses A* pathfinding to avoid obstacles
- Movement is asynchronous (doesn't block)
- Publishes
NPCMovementCompleteEventwhen destination reached - Automatically excludes other moving NPCs from pathfinding
update¶
update(delta_time: float) -> None
Update NPC movements and animations.
Parameters:
delta_time- Time since last update in seconds
Example:
Notes:
- Called automatically by PluginLoader each frame
- Updates NPC positions along paths
- Updates AnimatedNPC animations
- Publishes movement/animation complete events
Interaction¶
get_nearby_npc¶
get_nearby_npc(player_sprite: arcade.Sprite) -> tuple[arcade.Sprite, str, int] | None
Find the nearest NPC within interaction distance.
Parameters:
player_sprite- The player sprite
Returns:
- Tuple of
(sprite, name, dialog_level)orNone
Example:
player_sprite = context.player_plugin.get_player_sprite()
nearby = npc_plugin.get_nearby_npc(player_sprite)
if nearby:
sprite, name, dialog_level = nearby
print(f"Near {name} at level {dialog_level}")
Notes:
- Uses
NPC_INTERACTION_DISTANCEsetting - Returns closest NPC within range
- Skips NPCs that are currently moving
- Skips invisible NPCs
interact_with_npc¶
interact_with_npc(name: str) -> bool
Trigger interaction with a specific NPC.
Parameters:
name- Name of the NPC to interact with
Returns:
Trueif interaction started (dialog shown)
Example:
Notes:
- Retrieves NPC's current dialog
- Checks dialog conditions
- Shows dialog via DialogPlugin
- Marks NPC as interacted in current scene
mark_npc_as_interacted¶
mark_npc_as_interacted(npc_name: str, scene_name: str | None = None) -> None
Mark an NPC as interacted with in a specific scene.
Parameters:
npc_name- Name of the NPCscene_name- Scene name (defaults to current scene if not provided)
Example:
# Mark NPC as interacted in current scene
npc_plugin.mark_npc_as_interacted("merchant")
# Mark NPC as interacted in specific scene
npc_plugin.mark_npc_as_interacted("guard", "castle")
Notes:
- Interaction tracking is per-scene
- Used by
npc_interactedcondition check - Called automatically during normal interactions
has_npc_been_interacted_with¶
has_npc_been_interacted_with(npc_name: str, scene_name: str | None = None) -> bool
Check if an NPC has been interacted with in a specific scene.
Parameters:
npc_name- Name of the NPC to checkscene_name- Scene name (defaults to current scene if not provided)
Returns:
Trueif the NPC has been interacted with in the specified scene
Example:
if npc_plugin.has_npc_been_interacted_with("merchant"):
print("Player has talked to merchant before")
Notes:
- Check is scene-specific
- Returns False for NPCs never interacted with
- Used by
npc_interactedcondition
Visibility Management¶
show_npcs¶
show_npcs(npc_names: list[str]) -> None
Make hidden NPCs visible and add them to collision.
Parameters:
npc_names- List of NPC names to reveal
Example:
Notes:
- Makes sprite.visible = True
- Starts appear animation for AnimatedNPCs
- Adds NPC to wall collision list
- Used by
reveal_npcsaction
State Queries¶
get_npc_by_name¶
get_npc_by_name(name: str) -> NPCState | None
Get NPC state by name.
Parameters:
name- The NPC name
Returns:
- NPCState or None if not found
Example:
npc = npc_plugin.get_npc_by_name("merchant")
if npc:
print(f"Merchant at ({npc.sprite.center_x}, {npc.sprite.center_y})")
print(f"Dialog level: {npc.dialog_level}")
print(f"Moving: {npc.is_moving}")
get_npcs¶
get_npcs() -> dict[str, NPCState]
Get all registered NPCs.
Returns:
- Dictionary mapping NPC names to NPCState instances
Example:
for name, npc in npc_plugin.get_npcs().items():
print(f"{name}: level {npc.dialog_level}, moving={npc.is_moving}")
has_moving_npcs¶
has_moving_npcs() -> bool
Check if any NPCs are currently moving.
Returns:
Trueif at least one NPC is currently moving
Example:
Notes:
- Useful for blocking pausing during cutscenes
- Checks is_moving flag for all NPCs
Save/Load Support¶
get_save_state¶
get_save_state() -> dict[str, Any]
Return serializable state for saving.
Returns:
- Dictionary containing NPC states and interaction history
Example:
save_data = {
"player_position": (x, y),
"npc": npc_plugin.get_save_state(),
# ... other save data
}
Notes:
- Saves positions, visibility, dialog levels, animation flags
- Saves per-scene interaction history
restore_save_state¶
restore_save_state(state: dict[str, Any]) -> None
Phase 1: No metadata to restore for NPCs (sprites don't exist yet).
Parameters:
state- Dictionary containing saved NPC state
apply_entity_state¶
apply_entity_state(state: dict[str, Any]) -> None
Phase 2: Apply saved NPC state after sprites exist.
Parameters:
state- Dictionary containing saved NPC state
Example:
Notes:
- Restores positions, visibility, dialog levels
- Restores animation flags for AnimatedNPCs
- Restores per-scene interaction history
Scene Caching¶
cache_scene_state¶
cache_scene_state(scene_name: str) -> dict[str, Any]
Return state to cache during scene transitions.
Parameters:
scene_name- Name of the scene being cached
Returns:
- Dictionary containing NPC entity state for the scene
Notes:
- Only caches NPC entity state (positions, visibility, dialog levels)
- Interaction history is global and saved separately
restore_scene_state¶
restore_scene_state(scene_name: str, state: dict[str, Any]) -> None
Restore cached state when returning to a scene.
Parameters:
scene_name- Name of the scene being restoredstate- Previously cached state
Plugin Lifecycle¶
setup¶
setup(context: GameContext) -> None
Initialize the NPC plugin with game context.
Parameters:
context- Game context providing access to other plugins
Notes:
- Called automatically by PluginLoader
- Stores reference to game context
cleanup¶
cleanup() -> None
Clean up NPC resources when the scene unloads.
Notes:
- Clears all registered NPCs
- Clears dialog cache
- Called automatically by PluginLoader
reset¶
reset() -> None
Reset NPC plugin for new game.
Notes:
- Clears NPCs, dialogs, and interaction history
- Called when starting a new game
on_key_press¶
on_key_press(symbol: int, modifiers: int) -> bool
Handle key presses for NPC interaction.
Parameters:
symbol- Arcade key constantmodifiers- Modifier key bitfield
Returns:
Trueif input was consumed
Notes:
- Called automatically by PluginLoader
- Checks for NPC_INTERACTION_KEY press
- Finds nearby NPC and triggers interaction
load_from_tiled¶
load_from_tiled(tile_map: arcade.TileMap, arcade_scene: arcade.Scene) -> None
Load NPCs from Tiled map object layer.
Parameters:
tile_map- The loaded Tiled maparcade_scene- The arcade Scene to add NPCs to
Notes:
- Called automatically by PluginLoader
- Looks for "NPCs" object layer
- Creates AnimatedNPC sprites from object data
NPC Data Structures¶
NPCState¶
Runtime state tracking for a single NPC.
Attributes:
sprite: arcade.Sprite- The NPC's spritename: str- Unique identifierdialog_level: int- Current conversation level (default: 0)path: deque[tuple[float, float]]- Pathfinding waypointsis_moving: bool- Whether NPC is currently movingappear_event_emitted: bool- Tracks if appear event publisheddisappear_event_emitted: bool- Tracks if disappear event published
Example:
npc = npc_plugin.get_npc_by_name("merchant")
print(f"Dialog level: {npc.dialog_level}")
print(f"Position: ({npc.sprite.center_x}, {npc.sprite.center_y})")
print(f"Moving: {npc.is_moving}, Path length: {len(npc.path)}")
NPCDialogConfig¶
Configuration for NPC dialog at a specific conversation level.
Attributes:
text: list[str]- Dialog text pages to displayname: str | None- Optional display name for speakerconditions: list[dict[str, Any]] | None- Optional conditions to checkon_condition_fail: list[dict[str, Any]] | None- Actions to execute if conditions fail
Example:
dialog_config = NPCDialogConfig(
text=["Hello!", "Welcome to my shop."],
name="Merchant",
conditions=[{"check": "inventory_accessed", "equals": True}],
on_condition_fail=[
{"type": "dialog", "speaker": "Merchant", "text": ["Check your inventory first!"]}
]
)
Dialog Plugin¶
Dialog JSON Format¶
Dialog files are named {scene}_dialogs.json (e.g., casa_dialogs.json). The scene name is extracted from the filename.
{
"npc_name": {
"0": {
"name": "Display Name",
"text": ["First conversation page", "Second page"],
"conditions": [
{"check": "inventory_accessed", "equals": true}
],
"on_condition_fail": [
{"type": "dialog", "speaker": "NPC", "text": ["Not ready yet!"]}
]
},
"1": {
"name": "Display Name",
"text": ["Different dialog after progression"]
}
}
}
Structure:
- Top level: NPC name (key used in scripts)
- Second level: Dialog level (string, converted to int)
- Third level: Dialog configuration object
Fields:
name(optional) - Display name shown in dialog boxtext(required) - Array of dialog pagesconditions(optional) - Array of condition checkson_condition_fail(optional) - Array of actions if conditions fail
Scene-Aware Dialogs¶
Dialogs are organized by scene, allowing NPCs to have different conversations depending on location:
# Load scene-specific dialogs
npc_plugin.load_scene_dialogs("village")
# Get dialog for current scene
current_scene = context.scene_plugin.get_current_scene()
dialog_data = npc_plugin.get_dialog("merchant", 0, current_scene)
If no scene-specific dialog exists, the plugin falls back to "default" scene.
Dialog Conditions¶
Dialog can be conditional based on game state:
{
"merchant": {
"1": {
"name": "Merchant",
"text": ["You checked your inventory!"],
"conditions": [
{"check": "inventory_accessed", "equals": true}
],
"on_condition_fail": [
{"type": "dialog", "speaker": "Merchant", "text": ["Please check your inventory first!"]}
]
}
}
}
When conditions fail:
- Returns None for dialog_config
- Returns on_condition_fail actions
- Scripts can execute fallback actions
AnimatedNPC Sprites¶
AnimatedNPCs are specialized sprites with multi-directional animations and special effects.
Creating AnimatedNPC¶
from pedre.plugins.npc.sprites import AnimatedNPC
npc = AnimatedNPC(
sprite_sheet="characters/merchant.png",
center_x=320,
center_y=240,
scale=2.0,
tile_size=32,
# Walk animations (4 directions)
walk_up_frames=4,
walk_up_row=0,
walk_down_frames=4,
walk_down_row=1,
walk_left_frames=4,
walk_left_row=2,
walk_right_frames=4,
walk_right_row=3,
# Idle animations
idle_up_frames=1,
idle_up_row=4,
idle_down_frames=1,
idle_down_row=5,
# Special animations
appear_frames=6,
appear_row=8,
disappear_frames=6,
disappear_row=9
)
Animation Properties¶
Base Animation Properties (from sprite sheet):
walk_up_frames,walk_up_row- Walk upward animationwalk_down_frames,walk_down_row- Walk downward animationwalk_left_frames,walk_left_row- Walk left animationwalk_right_frames,walk_right_row- Walk right animationidle_up_frames,idle_up_row- Idle facing upidle_down_frames,idle_down_row- Idle facing downidle_left_frames,idle_left_row- Idle facing leftidle_right_frames,idle_right_row- Idle facing right
NPC-Specific Special Animations:
appear_frames,appear_row- Appear/spawn animationdisappear_frames,disappear_row- Disappear/fade animationinteract_up_frames,interact_up_row- Interaction facing upinteract_down_frames,interact_down_row- Interaction facing downinteract_left_frames,interact_left_row- Interaction facing leftinteract_right_frames,interact_right_row- Interaction facing right
Tiled Map Integration¶
NPCs can be placed in Tiled maps using object layers:
- Create an "NPCs" object layer
- Add point objects where NPCs should spawn
- Set custom properties on each object:
Required Properties:
name(string) - Unique NPC identifier (e.g., "merchant")sprite_sheet(string) - Path to sprite sheet (relative to assets)
Optional Properties:
tile_size(int) - Size of each tile in sprite sheet (default: from sheet)scale(float) - Sprite scale multiplier (default: 1.0)initially_hidden(bool) - Start invisible (default: false)- Animation properties (see above)
Example Tiled Properties:
name: merchant
sprite_sheet: characters/merchant.png
tile_size: 32
scale: 2.0
walk_up_frames: 4
walk_up_row: 0
walk_down_frames: 4
walk_down_row: 1
appear_frames: 6
appear_row: 8
Events¶
NPCInteractedEvent¶
Published when player interacts with an NPC.
Attributes:
npc_name: str- Name of the NPC that was interacted withdialog_level: int- Current conversation level
Script Trigger Example:
{
"trigger": {
"event": "npc_interacted",
"npc": "merchant"
},
"actions": [
{"type": "play_sfx", "file": "npc_interact.wav"}
]
}
Notes:
- Fires before dialog is shown
- The
npcfilter is optional (omit to trigger for any NPC)
NPCMovementCompleteEvent¶
Published when an NPC completes movement to target.
Attributes:
npc_name: str- Name of the NPC that completed movement
Script Trigger Example:
{
"trigger": {
"event": "npc_movement_complete",
"npc": "guard"
},
"actions": [
{"type": "dialog", "speaker": "Guard", "text": ["I've arrived!"]}
]
}
Notes:
- Fires when NPC reaches final waypoint
- Path is empty and is_moving is False
- The
npcfilter is optional
NPCAppearCompleteEvent¶
Published when an NPC completes appear animation.
Attributes:
npc_name: str- Name of the NPC that appeared
Script Trigger Example:
{
"trigger": {
"event": "npc_appear_complete",
"npc": "spirit"
},
"actions": [
{"type": "play_sfx", "file": "materialize.wav"},
{"type": "dialog", "speaker": "Spirit", "text": ["I have been summoned!"]}
]
}
Notes:
- Only AnimatedNPCs have appear animations
- Used internally by WaitForNPCsAppearAction
- The
npcfilter is optional (omit to trigger for any NPC) - Useful for triggering dialog or effects after dramatic entrances
NPCDisappearCompleteEvent¶
Published when an NPC completes disappear animation.
Attributes:
npc_name: str- Name of the NPC that disappeared
Script Trigger Example:
{
"trigger": {
"event": "npc_disappear_complete",
"npc": "ghost"
},
"actions": [
{"type": "play_sfx", "file": "vanish.wav"}
]
}
Notes:
- Only AnimatedNPCs have disappear animations
- NPC automatically hidden after animation
- The
npcfilter is optional
Actions¶
MoveNPCAction¶
Move one or more NPCs to a waypoint using pathfinding.
Type: move_npc
Parameters:
npcs: list[str]- List of NPC names to movewaypoint: str- Name of waypoint to move to
Example:
Notes:
- Initiates movement immediately
- Doesn't wait for arrival (use
wait_for_movementfor that) - Uses A* pathfinding to avoid obstacles
- Publishes
NPCMovementCompleteEventwhen done
RevealNPCsAction¶
Make hidden NPCs visible with appear animation.
Type: reveal_npcs
Parameters:
npcs: list[str]- List of NPC names to reveal
Example:
Notes:
- Makes sprite.visible = True
- Starts appear animation for AnimatedNPCs
- Adds to collision wall list
- Publishes
NPCAppearCompleteEventwhen animation done
AdvanceDialogAction¶
Advance an NPC's dialog level by 1.
Type: advance_dialog
Parameters:
npc: str- Name of the NPC
Example:
Notes:
- Increments dialog_level by 1
- Used to progress conversations
- New level immediately available for next interaction
SetDialogLevelAction¶
Set an NPC's dialog level to a specific value.
Type: set_dialog_level
Parameters:
npc: str- Name of the NPCdialog_level: int- The dialog level to set
Example:
Notes:
- Sets exact dialog level (doesn't increment)
- Useful for jumping to specific conversation states
- Can reset dialog by setting to 0
SetCurrentNPCAction¶
Set the current NPC tracking for dialog event attribution.
Type: set_current_npc
Parameters:
npc: str- Name of the NPC
Example:
Notes:
- Necessary for scripted dialogs (not from direct interaction)
- Ensures DialogClosedEvent has proper NPC attribution
- Should be used before showing scripted dialog
WaitForNPCMovementAction¶
Wait for NPC to complete movement.
Type: wait_for_movement
Parameters:
npc: str- Name of the NPC to wait for
Example:
[
{"type": "move_npc", "npcs": ["guard"], "waypoint": "gate"},
{"type": "wait_for_movement", "npc": "guard"},
{"type": "dialog", "speaker": "Guard", "text": ["I'm here!"]}
]
Notes:
- Pauses script until NPC stops moving
- Checks path is empty and is_moving is False
- Commonly used after move_npc
WaitForNPCsAppearAction¶
Wait for multiple NPCs to complete appear animations.
Type: wait_npcs_appear
Parameters:
npcs: list[str]- List of NPC names to wait for
Example:
[
{"type": "reveal_npcs", "npcs": ["spirit", "guardian"]},
{"type": "wait_npcs_appear", "npcs": ["spirit", "guardian"]},
{"type": "dialog", "speaker": "Spirit", "text": ["We have arrived!"]}
]
Notes:
- Only AnimatedNPCs have appear animations
- Waits for all NPCs in list
- Regular sprites complete immediately
WaitForNPCsDisappearAction¶
Wait for multiple NPCs to complete disappear animations.
Type: wait_for_npcs_disappear
Parameters:
npcs: list[str]- List of NPC names to wait for
Example:
[
{"type": "start_disappear_animation", "npcs": ["ghost"]},
{"type": "wait_for_npcs_disappear", "npcs": ["ghost"]},
{"type": "change_scene", "target_map": "next_area.tmx"}
]
Notes:
- Only AnimatedNPCs have disappear animations
- Waits for all NPCs in list
- Regular sprites complete immediately
StartDisappearAnimationAction¶
Start the disappear animation for one or more NPCs.
Type: start_disappear_animation
Parameters:
npcs: list[str]- List of NPC names to make disappear
Example:
Notes:
- Only AnimatedNPCs have disappear animations
- Waits for animations to complete before finishing
- Removes NPCs from wall collision list when done
- Publishes
NPCDisappearCompleteEventfor each NPC
Conditions¶
npc_interacted¶
Check if an NPC has been interacted with in a specific scene.
Parameters:
check:"npc_interacted"npc: Name of the NPC to checkscene: Optional scene name (defaults to current scene)equals: Expected value (default: true)
Example:
{
"trigger": {
"event": "portal_entered",
"portal": "exit"
},
"conditions": [
{
"check": "npc_interacted",
"npc": "guard"
}
]
}
Check if NOT interacted:
Use Cases:
- Gating progression behind conversations
- Quest prerequisites
- Conditional scene transitions
npc_dialog_level¶
Check an NPC's dialog level.
Parameters:
check:"npc_dialog_level"npc: Name of the NPCequals: Exact level to match
Example:
{
"trigger": {
"event": "npc_interacted",
"npc": "merchant"
},
"conditions": [
{
"check": "npc_dialog_level",
"npc": "merchant",
"equals": 2
}
]
}
Use Cases:
- Branching conversations
- Quest progression tracking
- Conditional NPC responses
Usage Examples¶
Basic NPC Interaction¶
# Check for nearby NPC
player_sprite = context.player_plugin.get_player_sprite()
nearby = npc_plugin.get_nearby_npc(player_sprite)
if nearby:
sprite, name, dialog_level = nearby
print(f"Near {name} at dialog level {dialog_level}")
Moving an NPC¶
{
"move_merchant": {
"scene": "village",
"trigger": {
"event": "object_interacted",
"object_name": "bell"
},
"actions": [
{"type": "move_npc", "npcs": ["merchant"], "waypoint": "market"},
{"type": "wait_for_movement", "npc": "merchant"},
{"type": "dialog", "speaker": "Merchant", "text": ["You called?"]}
]
}
}
Revealing Hidden NPCs¶
{
"summon_spirits": {
"scene": "forest",
"trigger": {
"event": "object_interacted",
"object_name": "ritual_circle"
},
"actions": [
{"type": "play_sfx", "file": "magic.wav"},
{"type": "reveal_npcs", "npcs": ["spirit_1", "spirit_2", "spirit_3"]},
{"type": "wait_npcs_appear", "npcs": ["spirit_1", "spirit_2", "spirit_3"]},
{"type": "dialog", "speaker": "Spirit", "text": ["You have summoned us!"]}
]
}
}
Progressive Conversations¶
{
"merchant_chat_1": {
"scene": "village",
"trigger": {
"event": "npc_interacted",
"npc": "merchant",
"dialog_level": 0
},
"actions": [
{"type": "dialog", "speaker": "Merchant", "text": ["Hello! Check your inventory."]},
{"type": "wait_for_dialog_close"},
{"type": "advance_dialog", "npc": "merchant"}
]
},
"merchant_chat_2": {
"scene": "village",
"trigger": {
"event": "npc_interacted",
"npc": "merchant",
"dialog_level": 1
},
"conditions": [
{"check": "inventory_accessed", "equals": true}
],
"actions": [
{"type": "dialog", "speaker": "Merchant", "text": ["Great! You found the inventory!"]},
{"type": "wait_for_dialog_close"},
{"type": "advance_dialog", "npc": "merchant"}
]
}
}
Coordinated NPC Movement¶
{
"guards_assemble": {
"scene": "castle",
"trigger": {
"event": "object_interacted",
"object_name": "alarm_bell"
},
"actions": [
{"type": "move_npc", "npcs": ["guard_1"], "waypoint": "gate"},
{"type": "move_npc", "npcs": ["guard_2"], "waypoint": "gate"},
{"type": "move_npc", "npcs": ["guard_3"], "waypoint": "gate"},
{"type": "wait_for_movement", "npc": "guard_1"},
{"type": "wait_for_movement", "npc": "guard_2"},
{"type": "wait_for_movement", "npc": "guard_3"},
{"type": "dialog", "speaker": "Captain", "text": ["All guards assembled!"]}
]
}
}
NPC Disappearing¶
{
"ghost_vanish": {
"scene": "haunted_house",
"trigger": {
"event": "dialog_closed",
"npc": "ghost"
},
"actions": [
{"type": "start_disappear_animation", "npcs": ["ghost"]},
{"type": "wait_for_npcs_disappear", "npcs": ["ghost"]},
{"type": "play_sfx", "file": "vanish.wav"},
{"type": "dialog", "speaker": "Narrator", "text": ["The ghost has vanished..."]}
]
}
}
Custom NPC Implementation¶
If you need to replace the NPC plugin with a custom implementation, you can extend the NPCBasePlugin abstract base class.
NPCBasePlugin¶
Location: src/pedre/plugins/npc/base.py
The NPCBasePlugin class defines the minimum interface that any NPC plugin must implement.
Required Methods¶
Your custom NPC plugin must implement these abstract methods:
from pedre.plugins.npc.base import NPCBasePlugin, NPCState
class CustomNPCPlugin(NPCBasePlugin):
"""Custom NPC implementation."""
name = "npc"
dependencies = ["pathfinding"]
def get_npcs(self) -> dict[str, NPCState]:
"""Get all NPCs."""
...
def load_scene_dialogs(self, scene_name: str) -> dict[str, Any]:
"""Load dialogs for a specific scene."""
...
def get_npc_by_name(self, name: str) -> NPCState | None:
"""Get NPC state by name."""
...
def move_npc_to_tile(self, npc_name: str, tile_x: int | float, tile_y: int | float) -> None:
"""Start moving an NPC to a target tile position."""
...
def has_npc_been_interacted_with(npc_name: str, scene_name: str | None = None) -> bool:
"""Check if an NPC has been interacted with."""
...
def advance_dialog(self, npc_name: str) -> int:
"""Advance the dialog level for an NPC."""
...
def show_npcs(self, npc_names: list[str]) -> None:
"""Make hidden NPCs visible and add them to collision."""
...
Registration¶
Register your custom NPC plugin using the @PluginRegistry.register decorator:
from pedre.plugins.registry import PluginRegistry
from pedre.plugins.npc.base import NPCBasePlugin
@PluginRegistry.register
class CustomNPCPlugin(NPCBasePlugin):
name = "npc"
dependencies = ["pathfinding"]
# ... implement all abstract methods ...
Notes on Custom Implementation¶
- Your custom plugin inherits from
BasePlugin(viaNPCBasePlugin), so you must implement the standard plugin lifecycle methods:setup(),cleanup(), andreset() - The
roleattribute is set to"npc_plugin"in the base class - Your implementation can use any dialog storage or movement plugin
- Register your custom NPC plugin in your project's
INSTALLED_PLUGINSsetting before the default"pedre.plugins.npc"to replace it
Example Custom Implementation:
# In myproject/plugins/custom_npc.py
from pedre.plugins.registry import PluginRegistry
from pedre.plugins.npc.base import NPCBasePlugin
@PluginRegistry.register
class DatabaseNPCPlugin(NPCBasePlugin):
"""NPC plugin that stores state in a database."""
name = "npc"
dependencies = ["pathfinding"]
def __init__(self):
self.db = Database()
# ... rest of initialization ...
def get_npc_by_name(self, name: str) -> NPCState | None:
# Query database for NPC
return self.db.query("SELECT * FROM npcs WHERE name = ?", name)
# ... implement other abstract methods ...
# In myproject/settings.py
INSTALLED_PLUGINS = [
"myproject.plugins.custom_npc", # Load custom NPC first
"pedre.plugins.camera",
"pedre.plugins.audio",
# ... rest of plugins (omit "pedre.plugins.npc") ...
]
See Also¶
- DialogPlugin - Conversation plugin
- ScriptPlugin - Event-driven scripting
- PathfindingPlugin - A* pathfinding plugin
- Configuration Guide
- Scripting Actions
- Scripting Conditions
- Scripting Events