Skip to content

Extending Pedre

Pedre is designed for extensibility. You can add custom actions, events, conditions, and even entire game plugins without modifying the framework code.

Extension System Overview

The Pedre extension system uses a plugin-style architecture with automatic discovery and registration:

  1. Define your custom component (action/event/condition/plugin)
  2. Register it using decorators (@ActionRegistry.register, @EventRegistry.register, etc.)
  3. Configure it in your project's settings.py to be loaded
  4. Use it in your JSON scripts or game code

What Can You Extend?

Actions

Create custom script actions that can be triggered from JSON scripts.

Use Cases:

  • Weather plugins (rain, snow, fog)
  • Custom UI elements (tooltips, notifications)
  • Game mechanics (crafting, combat, puzzles)
  • External integrations (analytics, achievements)

Learn More: Custom Actions

Events

Define custom events that scripts can listen for and react to.

Use Cases:

  • Custom player actions (jumping, crouching)
  • Environmental changes (time of day, weather)
  • Game state changes (level up, quest complete)
  • System notifications (achievement unlocked)

Learn More: Custom Events

Conditions

Create conditional checks for script execution.

Use Cases:

  • Player stats (health, level, attributes)
  • Time-based conditions (day/night, season)
  • Complex game state (quest progress, relationships)
  • Resource checks (money, items, energy)

Learn More: Custom Conditions

Plugins

Build entire game plugins that integrate with the framework lifecycle.

Use Cases:

  • Quest management
  • Combat plugin
  • Crafting plugin
  • Weather/time plugin
  • Relationship/faction plugin

Learn More: Custom Plugins

Extension Workflow

1. Create Your Extension

Define your custom component in a Python module:

# myproject/custom_actions.py
from pedre.actions import Action
from pedre.actions.registry import ActionRegistry

@ActionRegistry.register("custom_action")
class CustomAction(Action):
    def __init__(self, param: str):
        self.param = param
        self._executed = False

    @classmethod
    def from_dict(cls, data: dict):
        return cls(param=data["param"])

    def execute(self, context) -> bool:
        if not self._executed:
            # Your custom logic here
            self._executed = True
        return True

    def reset(self):
        self._executed = False

2. Register in Settings

Add your module to the appropriate INSTALLED_* setting:

# settings.py
from pedre.conf import global_settings

INSTALLED_ACTIONS = [
    *global_settings.INSTALLED_ACTIONS,  # Include built-in actions
    "myproject.custom_actions",           # Your custom actions
]

3. Use in Scripts

Your extension is now available in JSON scripts:

{
  "my_script": {
    "trigger": {"event": "scene_start"},
    "actions": [
      {
        "type": "custom_action",
        "param": "value"
      }
    ]
  }
}

Configuration Settings

INSTALLED_ACTIONS

List of Python module paths containing action classes.

INSTALLED_ACTIONS = [
    "pedre.plugins.audio.actions",
    "pedre.plugins.camera.actions",
    "pedre.plugins.dialog.actions",
    "myproject.custom_actions",  # Your custom actions
]

INSTALLED_EVENTS

List of Python module paths containing event classes.

INSTALLED_EVENTS = [
    "pedre.plugins.npc.events",
    "pedre.plugins.dialog.events",
    "myproject.custom_events",  # Your custom events
]

INSTALLED_CONDITIONS

List of Python module paths containing condition checker functions.

INSTALLED_CONDITIONS = [
    "pedre.plugins.inventory.conditions",
    "pedre.plugins.npc.conditions",
    "myproject.custom_conditions",  # Your custom conditions
]

INSTALLED_PLUGINS

List of Python module paths containing plugin classes.

INSTALLED_PLUGINS = [
    "pedre.plugins.audio.plugin",
    "pedre.plugins.dialog.plugin",
    "myproject.plugins.weather",  # Your custom plugin
]

Best Practices

Naming Conventions

  • Actions: Verb-based, lowercase with underscores (e.g., set_weather, spawn_enemy)
  • Events: Past tense, lowercase with underscores (e.g., weather_changed, enemy_spawned)
  • Conditions: Question-based, lowercase with underscores (e.g., is_raining, has_quest)
  • Plugins: Noun-based, PascalCase with "Plugin" suffix (e.g., WeatherPlugin, QuestPlugin)

Documentation

Always include docstrings explaining:

  • What the component does
  • What parameters it accepts
  • Return values and side effects
  • Usage examples

Error Handling

  • Actions: Return False to keep action active, True when complete
  • Conditions: Return False if checks fail, don't raise exceptions
  • Events: Use dataclasses for clear data structures
  • Plugins: Handle missing dependencies gracefully

Testing

Create unit tests for your extensions:

# tests/test_custom_actions.py
def test_custom_action():
    action = CustomAction(param="test")
    result = action.execute(mock_context)
    assert result == True

Examples

Simple Weather Action

@ActionRegistry.register("set_weather")
class SetWeatherAction(Action):
    def __init__(self, weather: str):
        self.weather = weather
        self._executed = False

    @classmethod
    def from_dict(cls, data: dict):
        return cls(weather=data["weather"])

    def execute(self, context) -> bool:
        if not self._executed:
            weather_plugin = context.get_plugin("weather")
            if weather_plugin:
                weather_plugin.set_weather(self.weather)
            self._executed = True
        return True

    def reset(self):
        self._executed = False

Weather Changed Event

@EventRegistry.register("weather_changed")
@dataclass
class WeatherChangedEvent:
    weather_type: str
    intensity: float

Weather Condition Check

@ConditionRegistry.register("is_weather")
def check_weather(data: dict[str, Any], context: GameContext) -> bool:
    weather_plugin = context.get_plugin("weather")
    if not weather_plugin:
        return False
    return weather_plugin.current_weather == data.get("weather")

Plugin Loader

The PluginLoader handles automatic loading and initialization of all extensions. It manages:

  • Dependency resolution
  • Initialization order
  • Lifecycle management (setup, reset, cleanup)
  • Event bus wiring

Next Steps

See Also