Version: 1.0.0 (Beta) Status: Built-in lifecycle enabled (registration, enable, disable)
The AnarchyRuleEngine Module API provides a stable, extensible abstraction for building optional modules that integrate with the core rule engine. Modules can:
- Register custom conditions and actions
- Provide placeholder resolvers
- Implement specialized features (Combat, Economy, Items, World, etc.)
- Operate independently without modifying core plugin behavior
- Be loaded/unloaded at runtime
Defines the contract that all modules must implement.
public interface RuleModule {
ModuleDescriptor getDescriptor();
void onEnable(ModuleContext context) throws Exception;
void onDisable();
}Lifecycle:
getDescriptor(): Provides module metadata (called during registration)onEnable(): Module initialization; happens after engine is fully initializedonDisable(): Module cleanup; happens during plugin shutdown or reload
Immutable metadata about a module. Contains:
- id: Unique identifier (String, required, non-empty)
- name: Human-readable name (String, required, non-empty)
- version: Module version (String, required, non-empty)
- description: What the module does (String, required, non-empty)
ModuleDescriptor descriptor = new ModuleDescriptor(
"combat",
"Combat Module",
"1.0.0",
"Provides combat-related conditions and actions"
);Immutable context passed to modules during lifecycle methods. Exposes only stable, vetted APIs:
| Method | Purpose | Type |
|---|---|---|
getPlugin() |
Access plugin for tasks, config, etc. | JavaPlugin |
getRuleEngine() |
Read-only access to rule engine | RuleEngine |
getApi() |
Public integration API for registering extensions | AnarchyRuleEngineApi |
getLogger() |
Logger for module logging | Logger |
registerCondition(String id, Collection<String> keys, ConditionFactory.ConditionCreator creator) |
NEW Register custom conditions (convenience method) | void |
registerAction(String type, ActionFactory.ActionCreator creator) |
NEW Register custom actions (convenience method) | void |
Design Note: ModuleContext intentionally exposes only stable public APIs to allow the engine to evolve without breaking module contracts.
Convenience Methods: The registerCondition() and registerAction() methods are convenience wrappers that delegate to the underlying AnarchyRuleEngineApi. They provide a cleaner API surface for modules registering extensions during initialization.
Manages module registration, enabled state, and lifecycle order.
Key Methods:
register(RuleModule module): Register a module (rejects nulls, duplicates)getModule(String id): Look up a module by id (returnsOptional)getModules(): Get all registered modules as unmodifiable listgetModuleCount(): Get count of registered modulesisRegistered(String id): Check if a module is registeredenableAll(ModuleContext context): Enable modules in registration orderdisableAll(Logger logger): Disable enabled modules in reverse registration orderisEnabled(String id): Check module runtime state (ENABLED/DISABLED)
Validation:
- Rejects null modules
- Rejects modules with null/empty id
- Rejects duplicate module ids
- Throws descriptive
IllegalArgumentExceptionon violation - Preserves registration order
public class CombatModule implements RuleModule {
private ModuleContext context;
@Override
public ModuleDescriptor getDescriptor() {
return new ModuleDescriptor(
"combat",
"Combat Module",
"1.0.0",
"Combat-related conditions and actions"
);
}
@Override
public void onEnable(ModuleContext context) throws Exception {
this.context = context;
context.getLogger().info("[CombatModule] Enabling...");
// Register custom conditions using convenience method
context.registerCondition(
"player-health",
Arrays.asList("player-health"),
section -> new PlayerHealthCondition(...)
);
// Register custom actions using convenience method
context.registerAction(
"damage-player",
actionData -> new DamagePlayerAction(...)
);
context.getLogger().info("[CombatModule] Enabled successfully");
}
@Override
public void onDisable() {
context.getLogger().info("[CombatModule] Disabling...");
// Cleanup resources
context.getLogger().info("[CombatModule] Disabled");
}
}ModuleRegistry registry = new ModuleRegistry();
// Register multiple modules
registry.register(new CombatModule());
registry.register(new ItemsModule());
registry.register(new EconomyModule());
// Look up a module
Optional<RuleModule> combat = registry.getModule("combat");
if (combat.isPresent()) {
RuleModule module = combat.get();
// Module operations
}
// Iterate all modules
for (RuleModule module : registry.getModules()) {
System.out.println(module.getDescriptor().getName());
}ModuleRegistry registry = new ModuleRegistry();
// Register modules
registry.register(new CombatModule());
registry.register(new ItemsModule());
// Enable all modules (in order)
for (RuleModule module : registry.getModules()) {
try {
// Create context from plugin state
ModuleContext context = new ModuleContext(
plugin,
ruleEngine,
api,
plugin.getLogger()
);
module.onEnable(context);
plugin.getLogger().info(
"Enabled module: " + module.getDescriptor().getId()
);
} catch (Exception e) {
plugin.getLogger().severe(
"Failed to enable module " + module.getDescriptor().getId()
+ ": " + e.getMessage()
);
// Handle error appropriately
}
}// Disable all modules (in reverse order)
List<RuleModule> modules = registry.getModules();
for (int i = modules.size() - 1; i >= 0; i--) {
try {
modules.get(i).onDisable();
} catch (Exception e) {
plugin.getLogger().warning(
"Error disabling module: " + e.getMessage()
);
}
}The API intentionally exposes only what's needed for modules to integrate cleanly. Stability is prioritized over convenience.
ModuleDescriptor and ModuleContext are immutable, preventing accidental state corruption and simplifying reasoning about module behavior.
All validation failures produce clear, descriptive error messages to help module developers understand what went wrong.
The API uses interfaces and plain Java types. No reflection, no magic, no runtime class loading.
Uses only Java 8 language features (no records, sealed classes, pattern matching, etc.).
The Module API is completely separate from core plugin initialization. No changes to existing startup/shutdown behavior.
The Module API is designed to support these future modules:
| Module | Purpose |
|---|---|
| CombatModule | Player damage, health, combat conditions/actions |
| ItemsModule | Custom item effects, item-based conditions |
| WorldModule | World manipulation, region-based features |
| EconomyModule | Economy conditions, money-based actions (with Vault integration) |
| GUIModule | Admin GUI for rule management |
Each would:
- Implement
RuleModuleinterface - Define unique id in
ModuleDescriptor - Register extensions via
ModuleContext.getApi() - Clean up resources in
onDisable()
✅ API Complete and Documented
- ✅ Module contract defined (
RuleModule) - ✅ Metadata support (
ModuleDescriptor) - ✅ Context and lifecycle management (
ModuleContext) - ✅ Registration convenience methods (
registerCondition(),registerAction()) - ✅ Registration and discovery (
ModuleRegistry) - ✅ Lifecycle execution integrated into plugin startup/shutdown
- ✅ Built-in
CoreModuleandCustomItemsModuleregistered by default
Modules can integrate with AnarchyRuleEngine via ModuleContext:
// Convenience methods (recommended for registration)
context.registerCondition("my-condition", Arrays.asList("my-condition"),
section -> new MyCondition(...));
context.registerAction("my-action",
data -> new MyAction(...));
// Alternative: Direct API access (also supported)
AnarchyRuleEngineApi api = context.getApi();
api.registerCondition("my-condition", Arrays.asList("my-condition"),
section -> new MyCondition(...));
api.registerAction("my-action",
data -> new MyAction(...));
api.registerPlaceholder(
new MyPlaceholderResolver());
// Query and control rules
List<Rule> rules = api.getRules();
RuleExecutionResult result = api.evaluate(context);
api.enableRule("my-rule");
api.disableRule("my-rule");
api.resetRule("my-rule");- Registration: O(1) per module
- Lookup: O(1) via HashMap
- Iteration: O(n) unmodifiable copy
- Memory: Minimal; stores only module references and metadata
- Thread Safety: Not thread-safe; use on main Bukkit thread only
- Java: 8+
- Paper: 1.16.5+
- Bukkit: 1.16.5+
- Dependencies: None (uses only core AnarchyRuleEngine APIs)
public class ItemsModule implements RuleModule {
@Override
public ModuleDescriptor getDescriptor() {
return new ModuleDescriptor(
"items",
"Items Module",
"1.0.0",
"Custom item properties and conditions"
);
}
@Override
public void onEnable(ModuleContext context) throws Exception {
context.getLogger().info("[ItemsModule] Loading custom items...");
// Register custom conditions for item checks using convenience method
context.registerCondition("item-name",
Arrays.asList("item-name"),
section -> new ItemNameCondition(...));
// Register actions that modify items using convenience method
context.registerAction("modify-item",
data -> new ModifyItemAction(...));
context.getLogger().info("[ItemsModule] Items module loaded");
}
@Override
public void onDisable() {
// Items module has no persistent state to clean up
}
}Q: Can modules be loaded dynamically at runtime? A: Modules are currently registered during plugin startup. Runtime hot-loading from external module jars is not implemented.
Q: Can modules depend on each other? A: Not directly. Modules are independent. If needed, modules can coordinate through shared conditions/actions registered in the API.
Q: What if a module throws an exception during enable? A: The exception is propagated to the caller. It's up to the plugin to decide: skip the module, disable plugin, or handle gracefully.
Q: Can modules access internal services? A: No, intentionally. Only stable public APIs are exposed via ModuleContext to prevent tight coupling and allow future refactoring.
Q: Is the registry thread-safe? A: No. All operations must occur on the main Bukkit thread during startup/shutdown.