ScriptPlugin¶
Event-driven scripting plugin for cutscenes and interactive sequences.
Location¶
- Implementation: src/pedre/plugins/script/plugin.py
- Base class: src/pedre/plugins/script/base.py
- Events: src/pedre/plugins/script/events.py
- Conditions: src/pedre/plugins/script/conditions.py
Configuration¶
The ScriptPlugin uses the following setting from pedre.conf.settings:
ASSETS_HANDLE- Base path for asset loading (default: "assets")
Public API¶
Script Loading¶
All scripts are loaded globally during plugin setup. The ScriptPlugin automatically scans the scripts directory for all *_scripts.json files and loads them at initialization time. This makes scripts available for condition checking across all scenes, with the scene field in each script definition controlling when it can actually execute.
Automatic Loading:
Scripts are loaded automatically when ScriptPlugin initializes:
# Scripts are loaded during setup
script_plugin = ScriptPlugin()
script_plugin.setup(context) # Loads all scripts from scripts directory
Script File Naming:
Script files should follow the naming pattern *_scripts.json:
Notes:
- All scripts from all files are loaded into a single global registry
- The
scenefield in each script controls when it can execute - Scripts are available for condition checking regardless of current scene
- Scripts are reloaded on reset (new game or load game)
Script Execution¶
update¶
update(delta_time: float) -> None
Update active action sequences (called every frame).
Parameters:
delta_time- Time since last update in seconds
Example:
Notes:
- Called automatically by PluginLoader each frame
- Updates all currently executing script action sequences
- Removes completed sequences and publishes ScriptCompleteEvent
State Queries¶
get_scripts¶
get_scripts() -> dict[str, Script]
Get all loaded scripts.
Returns:
- Dictionary mapping script names to Script instances
Example:
all_scripts = script_plugin.get_scripts()
for name, script in all_scripts.items():
print(f"{name}: completed={script.completed}, has_run={script.has_run}")
Save/Load Support¶
get_save_state¶
get_save_state() -> dict[str, Any]
Return serializable state for saving.
Returns:
- Dictionary containing completed scripts and run-once execution history
Example:
save_data = {
"player_position": (x, y),
"script": script_plugin.get_save_state(),
# ... other save data
}
Notes:
- Saves lists of completed scripts and run-once scripts that have executed
- Active running scripts are NOT saved and will restart on load
restore_save_state¶
restore_save_state(state: dict[str, Any]) -> None
Restore script plugin state from save file.
Parameters:
state- Dictionary containing saved script state
Example:
Notes:
- Restores completion flags and run-once history
- Critical for preventing one-time events from re-running
Plugin Lifecycle¶
setup¶
setup(context: GameContext) -> None
Initialize the script plugin with game context.
Parameters:
context- Game context providing access to other plugins
Notes:
- Called automatically by PluginLoader
- Loads all scripts globally from scripts directory
- Registers event handlers for all script triggers
cleanup¶
cleanup() -> None
Clean up script plugin resources.
Notes:
- Unregisters all event handlers
- Clears scripts and active sequences
- Called automatically by PluginLoader
reset¶
reset() -> None
Reset script plugin for new game.
Notes:
- Clears all runtime state (active sequences, completion flags)
- Reloads all scripts from disk
- Re-registers event handlers
- Called when starting a new game or loading a saved game
Script Data Structures¶
Script¶
Container for action sequences with trigger conditions and metadata.
Attributes:
trigger: dict[str, Any] | None- Event specification that triggers this scriptconditions: list[dict[str, Any]]- List of condition dictionaries that must all be truescene: str | None- Optional scene name where this script can runrun_once: bool- If True, script only executes once per game sessionactions: list[dict[str, Any]]- List of action dictionaries to execute in sequenceon_condition_fail: list[dict[str, Any]]- Optional actions to execute when conditions failhas_run: bool- Tracks if this script has started (for run_once prevention)completed: bool- Tracks if this script has fully completed all actions
Example:
script = Script(
trigger={"event": "dialog_closed", "npc": "martin", "dialog_level": 1},
conditions=[{"check": "inventory_accessed", "equals": True}],
scene="village",
run_once=True,
actions=[
{"type": "dialog", "speaker": "martin", "text": ["Hello!"]},
{"type": "wait_for_dialog_close"},
{"type": "move_npc", "npcs": ["martin"], "waypoint": "town_square"}
]
)
Script Plugin¶
Script JSON Format¶
Scripts are defined in JSON files located in assets/scripts/ with the naming pattern *_scripts.json:
{
"script_name": {
"trigger": {
"event": "npc_interacted",
"npc": "merchant",
"dialog_level": 0
},
"conditions": [
{"check": "inventory_accessed", "equals": true}
],
"scene": "village",
"run_once": true,
"actions": [
{"type": "dialog", "speaker": "merchant", "text": ["Hello!"]},
{"type": "wait_for_dialog_close"},
{"type": "advance_dialog", "npc": "merchant"}
],
"on_condition_fail": [
{"type": "dialog", "speaker": "merchant", "text": ["Check your inventory first!"]}
]
}
}
Structure:
- Top level: Script name (unique identifier)
trigger: Event specification with filtersconditions: Optional array of condition checksscene: Optional scene restrictionrun_once: Optional one-time execution flagactions: Array of actions to executeon_condition_fail: Optional fallback actions
Script Fields¶
| Field | Type | Required | Description |
|---|---|---|---|
trigger |
object | Yes | Event specification that triggers this script |
conditions |
array | No | Conditions to check before running |
scene |
string | No | Only run in this scene/map (omit for global scripts that run anywhere) |
run_once |
bool | No | Only execute once (default: false) |
actions |
array | Yes | Sequence of actions to execute |
on_condition_fail |
array | No | Actions to execute when conditions fail |
Trigger Object¶
The trigger object specifies which event activates the script:
Fields:
event(required) - Event type name (e.g., "npc_interacted", "dialog_closed")- Additional fields act as filters (all must match for trigger to fire)
Available Event Types:
npc_interacted- Player interacts with NPCdialog_opened- Dialog window opensdialog_closed- Dialog window closesinventory_closed- Inventory closesitem_acquired- Item added to inventoryitem_consumed- Item consumed from inventoryitem_acquisition_failed- Item acquisition failedobject_interacted- Player interacts with objectnpc_movement_complete- NPC finishes movingnpc_appear_complete- NPC appear animation finishesnpc_disappear_complete- NPC disappear animation finishesportal_entered- Player enters portal zonescene_start- Scene/map finishes loadingscript_complete- Another script finishes
For detailed information on each event type, see Event Types.
Condition Checking¶
Conditions must all evaluate to true for the script to execute:
{
"conditions": [
{"check": "inventory_accessed", "equals": true},
{"check": "npc_dialog_level", "npc": "merchant", "equals": 2}
]
}
When conditions fail and on_condition_fail is defined:
- Script's main actions are skipped
on_condition_failactions execute instead- Useful for feedback messages or alternative paths
For available condition types, see Conditions.
Global Script Loading¶
All scripts are loaded globally during plugin initialization:
- ScriptPlugin scans
assets/scripts/for all*_scripts.jsonfiles - All scripts are loaded into a single registry
- Event triggers are registered with the EventBus
- Scripts can be referenced in conditions across all scenes
Scene Restriction:
The optional scene field controls when a script can execute:
{
"village_only_script": {
"scene": "village",
"trigger": {"event": "npc_interacted", "npc": "merchant"},
"actions": [...]
},
"global_script": {
"trigger": {"event": "item_acquired", "item_id": "key"},
"actions": [...]
}
}
- Scene-specific scripts (with
scenefield): Only execute in the specified scene - Global scripts (without
scenefield): Can execute in any scene - Conditions can reference scripts from any scene, regardless of where they're defined
Example - Cross-Scene Condition:
{
"castle_gate": {
"scene": "castle",
"trigger": {"event": "portal_entered", "portal": "gate"},
"conditions": [
{"check": "script_completed", "script": "village_quest_complete"}
],
"actions": [
{"type": "change_scene", "target_map": "throne_room.tmx"}
]
}
}
This allows checking if a script from another scene has completed.
Events¶
ScriptCompleteEvent¶
Published when a script completes execution.
Attributes:
script_name: str- Name of the script that completed
Script Trigger Example:
{
"followup": {
"trigger": {
"event": "script_complete",
"script": "intro_cutscene"
},
"actions": [
{"type": "reveal_npcs", "npcs": ["guard"]}
]
}
}
Notes:
- Fires when all actions in a script have finished executing
- The
scriptfilter is optional (omit to trigger for any script completion) - Useful for chaining complex multi-stage sequences
Conditions¶
script_completed¶
Check if a specific script has fully completed all its actions.
Parameters:
check:"script_completed"script: Name of the script to check
Example:
{
"trigger": {
"event": "portal_entered",
"portal": "exit"
},
"conditions": [
{
"check": "script_completed",
"script": "main_quest_finale"
}
]
}
Use Cases:
- Gating progression behind script completion
- Quest prerequisites
- Story progression tracking
- Conditional scene transitions
Usage Examples¶
Basic Script Trigger¶
{
"greet_merchant": {
"scene": "village",
"trigger": {
"event": "npc_interacted",
"npc": "merchant",
"dialog_level": 0
},
"run_once": true,
"actions": [
{"type": "dialog", "speaker": "Merchant", "text": ["Welcome!"]},
{"type": "wait_for_dialog_close"},
{"type": "advance_dialog", "npc": "merchant"}
]
}
}
Conditional Script with Fallback¶
{
"check_inventory": {
"scene": "village",
"trigger": {
"event": "npc_interacted",
"npc": "merchant",
"dialog_level": 1
},
"conditions": [
{"check": "inventory_accessed", "equals": true}
],
"actions": [
{"type": "dialog", "speaker": "Merchant", "text": ["Good! You found your inventory!"]}
],
"on_condition_fail": [
{"type": "dialog", "speaker": "Merchant", "text": ["Please check your inventory first."]}
]
}
}
Chained Scripts¶
{
"cutscene_part1": {
"scene": "forest",
"trigger": {
"event": "object_interacted",
"object_name": "altar"
},
"run_once": true,
"actions": [
{"type": "play_sfx", "file": "magic.wav"},
{"type": "reveal_npcs", "npcs": ["spirit"]},
{"type": "wait_npcs_appear", "npcs": ["spirit"]}
]
},
"cutscene_part2": {
"trigger": {
"event": "script_complete",
"script": "cutscene_part1"
},
"actions": [
{"type": "dialog", "speaker": "Spirit", "text": ["Who dares summon me?"]},
{"type": "wait_for_dialog_close"},
{"type": "start_disappear_animation", "npcs": ["spirit"]}
]
}
}
Portal Transition¶
{
"forest_portal": {
"trigger": {
"event": "portal_entered",
"portal": "to_forest"
},
"conditions": [
{"check": "npc_interacted", "npc": "guard", "equals": true}
],
"actions": [
{"type": "change_scene", "target_map": "forest.tmx", "spawn_waypoint": "from_village"}
],
"on_condition_fail": [
{"type": "dialog", "speaker": "Narrator", "text": ["The path is blocked."]}
]
}
}
Scene Initialization¶
{
"village_intro": {
"scene": "village",
"trigger": {
"event": "scene_start"
},
"run_once": true,
"actions": [
{"type": "play_music", "file": "village_theme.ogg"},
{"type": "dialog", "speaker": "Narrator", "text": ["Welcome to the village!"]}
]
}
}
Custom Script Implementation¶
If you need to replace the script plugin with a custom implementation, you can extend the ScriptBasePlugin abstract base class.
ScriptBasePlugin¶
Location: src/pedre/plugins/script/base.py
The ScriptBasePlugin class defines the minimum interface that any script plugin must implement.
Required Methods¶
Your custom script plugin must implement these abstract methods:
from pedre.plugins.script.base import ScriptBasePlugin, Script
class CustomScriptPlugin(ScriptBasePlugin):
"""Custom script implementation."""
name = "script"
dependencies = []
def get_scripts(self) -> dict[str, Script]:
"""Get all loaded scripts."""
...
Registration¶
Register your custom script plugin using the @PluginRegistry.register decorator:
from pedre.plugins.registry import PluginRegistry
from pedre.plugins.script.base import ScriptBasePlugin
@PluginRegistry.register
class CustomScriptPlugin(ScriptBasePlugin):
name = "script"
dependencies = []
# ... implement all abstract methods ...
Notes on Custom Implementation¶
- Your custom plugin inherits from
BasePlugin(viaScriptBasePlugin), so you must implement the standard plugin lifecycle methods:setup(),cleanup(), andreset() - The
roleattribute is set to"script_plugin"in the base class - Your implementation can use any script storage or execution plugin
- Register your custom script plugin in your project's
INSTALLED_PLUGINSsetting before the default"pedre.plugins.script"to replace it
Example Custom Implementation:
# In myproject/plugins/custom_script.py
from pedre.plugins.registry import PluginRegistry
from pedre.plugins.script.base import ScriptBasePlugin
@PluginRegistry.register
class DatabaseScriptPlugin(ScriptBasePlugin):
"""Script plugin that stores state in a database."""
name = "script"
dependencies = []
def __init__(self):
self.db = Database()
# ... rest of initialization ...
def get_scripts(self) -> dict[str, Script]:
# Query database for scripts
return self.db.query("SELECT * FROM scripts")
# ... implement other abstract methods ...
# In myproject/settings.py
INSTALLED_PLUGINS = [
"myproject.plugins.custom_script", # Load custom script first
"pedre.plugins.camera",
"pedre.plugins.audio",
# ... rest of plugins (omit "pedre.plugins.script") ...
]
See Also¶
- NPCPlugin - NPC interactions and dialog
- DialogPlugin - Conversation plugin
- Scripting Guide - Event-driven scripting documentation
- Scripting Basics - Script structure and organization
- Event Types - Available event triggers
- Conditions - Script condition plugin
- Actions - Available script actions