PhantomDungeons is built around a first-class module system. Every built-in feature, such as economy, enchants, wands, withdraw, crystals, has its own PhantomModule, with no special internal privileges over modules you write yourself.
The same API is open to external plugins. You can write a module that lives inside your own .jar but runs inside the PhantomDungeons plugin lifecycle:
DungeonManager(constructor) └─ ModuleRegistry.register(module)<-yourmoduleregisteredhereDungeonManager.initialize() └─ ModuleRegistry(topologicalsortbydependencies+priority) └─ for each module in order: module.onEnable(ModuleContext) └─ checkConfigEnabled()<-readsmodules.yml └─ enable()<-YOURcoderunshereregistermodule's Listeners/dungeonsadminreload └─ ModuleRegistry.reloadAll() └─ module.onReload(ModuleContext) → reload()plugindisable └─ ModuleRegistry.shutdownAll()(reverseorder) └─ module.onDisable(ModuleContext) → disable()
The ModuleContext passed into every lifecycle method gives you access to:
Field
Type
What it is
plugin
JavaPlugin
The PhantomDungeons plugin instance
configManager
ConfigurationManager
Loads/caches YAML files from the data folder
moduleRegistry
ModuleRegistry
Retrieve sibling modules by class or ID
playerRepository
PlayerRepository
Async database access for player data, this is the "cache" layer and NOT the database layer.
playerManager
PlayerManager
In-memory online player state
Quick Start
Step 1 - Extend AbstractPhantomModule
Always extend AbstractPhantomModule rather than implementing PhantomModule directly. It handles the boilerplate (context assignment, logger setup, enabled flag, isEnabled()) and exposes clean enable() / reload() / disable() hooks.
Step 2 - Register it with the ModuleRegistry
Get the registry from DungeonManager and call register()beforeDungeonManager.initialize() runs. The right place is your plugin's onEnable(), immediately after PhantomDungeons is confirmed to be loaded.
Declare the hard dependency in plugin.yml so Bukkit guarantees load order:
That's it, your module now runs inside the PhantomDungeons lifecycle.
Full Module Reference
AbstractPhantomModule fields available inside enable() / reload() / disable()
Field
Type
Description
plugin
JavaPlugin
The PhantomDungeons plugin instance.
context
ModuleContext
Full lifecycle context (see table above).
logger
Logger
Pre-prefixed logger: [MyModule] …
enabled
boolean
Set by checkConfigEnabled(). Read before doing real work.
Lifecycle methods
Method
When called
Notes
checkConfigEnabled()
Before enable(), always
Override to set enabled = false if modules.yml says off.
enable()
Once on startup if enabled
Throw ModuleInitException for critical failures.
reload()
On /pd reload
Flush caches, re-read config.
disable()
On plugin shutdown
Cancel tasks, release resources, clear API singletons.
getListeners()
After enable()
Return Bukkit listeners; they are auto-registered.
Logging helpers (use these instead of Bukkit.getLogger())
Common Patterns
Toggle with modules.yml
Follow the same pattern all built-in sub-modules use, the read the flag in checkConfigEnabled() so the registry skips enable() automatically:
Add the corresponding key to your copy of modules.yml (or instruct server admins to add it):
Declare dependencies
If your module depends on, say, EconomyModule and ZoneModule, declare their IDs. The registry performs a topological sort ,your enable() is guaranteed to run after both dependencies are ready:
Control initialisation priority
Among modules with no unsatisfied dependencies, lower priority numbers initialise first (default is 100):
Access a sibling module
Or use the static helper from AbstractPhantomModule:
Expose a public API (recommended)
All built-in modules (Economy, Wands, Withdraw, Leveling) expose a typed API via a singleton provider. Reproduce the same three-file pattern:
Then hook it up inside the module:
External callers then use:
Example
Throwing ModuleInitException
Use ModuleInitException to signal that your module cannot start. By default the exception is critical, meaning PhantomDungeons will log the error and continue without your module (it will not crash the whole server). Pass false as the third argument only if failure should abort startup entirely.
Accessing PhantomDungeons APIs from Your Module
Once inside enable(), context exposes everything you need without touching PhantomDungeons.getInstance():
import me.fergs.phantomdungeons.modules.api.AbstractPhantomModule;
import me.fergs.phantomdungeons.modules.api.module.ModuleInitException;
public final class MyModule extends AbstractPhantomModule {
public static final String ID = "my-module";
public MyModule() {
super(ID, "My Module"); // your module id
}
@Override
protected void enable() throws ModuleInitException {
info("My module is starting up!");
// initialise managers, listeners, commands…
}
@Override
protected void reload() {
info("Reloading configuration...");
}
@Override
protected void disable() {
info("Shutting down...");
}
}
import me.fergs.phantomdungeons.PhantomDungeons;
import me.fergs.phantomdungeons.managers.DungeonManager;
public final class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
PhantomDungeons pd = (PhantomDungeons) getServer().getPluginManager().getPlugin("PhantomDungeons");
if (pd == null) {
getLogger().severe("PhantomDungeons not found — disabling.");
getServer().getPluginManager().disablePlugin(this);
return;
}
DungeonManager dm = pd.getDungeonManager();
if (dm == null) {
getLogger().severe("DungeonManager not ready — disabling.");
getServer().getPluginManager().disablePlugin(this);
return;
}
dm.getModuleRegistry().register(new MyModule());
// PhantomDungeons initialises after all plugins have enabled,
// so your module will be picked up in the same initialisation pass.
}
}
depend: [PhantomDungeons]
info("Player joined zone"); // [My Module] Player joined zone
warn("Config key missing"); // [My Module] Config key missing
severe("Database error"); // [My Module] Database error
public interface MyModuleAPI {
static MyModuleAPI get() {
return MyModuleAPIProvider.getInstance();
}
int getScore(Player player);
void addScore(Player player, int amount);
boolean isAvailable();
}
public final class MyModuleAPIProvider {
private static MyModuleAPI instance;
private MyModuleAPIProvider() {}
public static MyModuleAPI getInstance() {
if (instance == null) throw new IllegalStateException("MyModuleAPI not initialised");
return instance;
}
public static void setInstance(MyModuleAPI api) { instance = api; }
public static void clearInstance() { instance = null; }
public static boolean isAvailable() { return instance != null; }
}
public final class MyModuleAPIImpl implements MyModuleAPI {
private final MyManager manager;
public MyModuleAPIImpl(MyManager manager) { this.manager = manager; }
@Override public int getScore(Player p) { return manager.getScore(p); }
@Override public void addScore(Player p, int amt) { manager.addScore(p, amt); }
@Override public boolean isAvailable() { return true; }
}