The plugin system allows developers to extend Quest Rust with entirely new puzzle types (e.g. physics simulations, cryptographic challenges, complex state machines) without having to modify the core game engine.
Instead of hardcoding how a puzzle determines whether the player's input is correct, puzzles can declare an optional plugin_id. During the game session, when a player attempts an action, the engine looks up the plugin registered under that ID and delegates the evaluation to it.
If a puzzle does not declare a plugin_id, it defaults to the built-in core_logic evaluator (the standard AND/OR/NOT condition chains).
To create a new puzzle type, you implement the PuzzlePlugin trait found in src/plugin/mod.rs.
A plugin must define three methods:
id(&self) -> &str— The unique string ID for this plugin.describe(&self) -> PluginMeta— A description of what the plugin does.evaluate(&self, input: &str, context: &HashMap<&str, bool>) -> EvalResult— The actual evaluation logic.
The evaluate method returns an EvalResult which can be:
Satisfied: The player's input successfully solved/progressed the puzzle.Unsatisfied { reason }: The player's input was incorrect (thereasonstring can be used to hint the player).Error { message }: The plugin encountered an internal error during evaluation.
Suppose you want to add a puzzle type where the player must input the correct SHA-256 hash of a specific string.
use smart_contract_game::plugin::{EvalResult, PluginMeta, PuzzlePlugin};
use std::collections::HashMap;
pub struct CryptoPlugin;
impl PuzzlePlugin for CryptoPlugin {
fn id(&self) -> &str {
"crypto_sha256"
}
fn describe(&self) -> PluginMeta {
PluginMeta {
name: "SHA-256 Crypto Challenge".to_string(),
version: "1.0.0".to_string(),
description: "Requires the player to submit a valid SHA-256 hash.".to_string(),
}
}
fn evaluate(&self, input: &str, _context: &HashMap<&str, bool>) -> EvalResult {
// In a real plugin, the expected hash might be passed via context or puzzle data.
let expected_hash = "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824";
if input.trim() == expected_hash {
EvalResult::Satisfied
} else {
EvalResult::Unsatisfied {
reason: "Invalid hash. Please try again.".to_string(),
}
}
}
}Once your plugin struct is implemented, you must register it with the engine at startup.
In src/main.rs:
use smart_contract_game::plugin::{core_logic_plugin::CoreLogicPlugin, PluginRegistry};
// use my_custom_plugins::CryptoPlugin;
let mut plugin_registry = PluginRegistry::new();
// Register the built-in logic (always recommended)
plugin_registry.register(Box::new(CoreLogicPlugin)).unwrap();
// Register your custom plugin
// plugin_registry.register(Box::new(CryptoPlugin)).unwrap();
// The registry is then passed to the GameEngine (implementation depends on engine structure)When writing a puzzle JSON file, simply include the plugin_id matching your plugin's id().
{
"id": "hash-puzzle-1",
"plugin_id": "crypto_sha256",
"description": "Calculate the SHA-256 hash of the word 'hello'.",
"conditions": [],
"effects": [
{ "AwardScore": 500 }
]
}- Error:
plugin 'XYZ' not foundMake sure you registered the plugin inmain.rsand that theplugin_idin your puzzle JSON exactly matches the string returned by your plugin'sid()method. - Error:
plugin 'XYZ' is already registeredYou accidentally calledregistry.register()twice for the same plugin ID. Plugin IDs must be globally unique. - CLI Verification
Run
cargo run -- --list-pluginsto view all plugins currently registered in the game, confirming that your new plugin was initialized correctly.