Plugin Phases
Detailní dokumentace fází životního cyklu pluginu.
---
Přehled Fází
PluginState.NONE (konstruktor)
↓
preLoad() [async]
↓
PluginState.SETUP (setup())
↓
[Načítání světů]
↓
AllWorldsLoadedEvent
↓
PluginState.START (start())
↓
PluginState.ENABLED [Server běží]
↓
PluginState.SHUTDOWN (shutdown())
↓
PluginState.DISABLED (cleanup())
---
PluginState Enum
Z dekompilovaného kódu:
public enum PluginState {
NONE, // Plugin vytvořen, ale ne inicializován
SETUP, // Probíhá setup()
START, // Probíhá start()
ENABLED, // Plugin plně aktivní
SHUTDOWN, // Probíhá shutdown()
DISABLED // Plugin vypnutý
}
---
Fáze 1: Konstruktor
Volání
Plugin je instancován s JavaPluginInit objektem.
public class MyPlugin extends JavaPlugin { public MyPlugin(@Nonnull JavaPluginInit init) {
super(init); // Musí být voláno!
}
}
Co je Dostupné
V konstruktoru je k dispozici:
public MyPlugin(JavaPluginInit init) {
super(init); // Dostupné v konstruktoru:
getLogger(); // HytaleLogger
getDataDirectory(); // Path k datové složce
getManifest(); // PluginManifest
getIdentifier(); // PluginIdentifier
}
Co Nedělat
// ŠPATNĚ - neregistrovat komponenty v konstruktoru
public MyPlugin(JavaPluginInit init) {
super(init);
// NEREGISTROVAT zde!
getEntityStoreRegistry().registerComponent(...); // ❌
}
---
Fáze 2: preLoad() [Volitelné]
Účel
Async načítání konfigurace před setup().
Implementace
public class MyPlugin extends JavaPlugin {
private final Config config; public MyPlugin(JavaPluginInit init) {
super(init);
// Registrace config souboru - musí být v konstruktoru!
this.config = withConfig(MyConfig.CODEC);
}
}
Z Dekompilovaného Kódu
@Nullable
public CompletableFuture preLoad() {
if (this.configs.isEmpty()) {
return null;
} CompletableFuture>[] futures = new CompletableFuture[this.configs.size()];
for (int i = 0; i < this.configs.size(); i++) {
futures[i] = this.configs.get(i).load();
}
return CompletableFuture.allOf(futures);
}
withConfig()
@Nonnull
protected final Config withConfig(@Nonnull BuilderCodec configCodec) {
return this.withConfig("config", configCodec); // config.json
}@Nonnull
protected final Config withConfig(@Nonnull String name, @Nonnull BuilderCodec configCodec) {
if (this.state != PluginState.NONE) {
throw new IllegalStateException("Must be called before setup"); // ⚠️
}
Config config = new Config<>(this.dataDirectory, name, configCodec);
this.configs.add(config);
return config;
}
---
Fáze 3: setup()
Kdy je Voláno
Po preLoad(), před načtením světů.
Z Dekompilovaného Kódu
protected void setup0() {
if (this.state != PluginState.NONE && this.state != PluginState.DISABLED) {
throw new IllegalArgumentException(String.valueOf(this.state));
} this.state = PluginState.SETUP;
try {
this.setup();
} catch (Throwable var2) {
this.logger.at(Level.SEVERE).withCause(var2).log("Failed to setup plugin %s", this.identifier);
this.state = PluginState.DISABLED; // Plugin se deaktivuje při chybě
}
}
Co Registrovat
@Override
protected void setup() {
instance = this; // Singleton pattern // 1. Komponenty
this.myComponentType = getEntityStoreRegistry().registerComponent(
MyComponent.class,
MyComponent::new
);
// 2. Systémy
getEntityStoreRegistry().registerSystem(new MySystem());
// 3. Příkazy
getCommandRegistry().registerCommand(new MyCommand());
// 4. Eventy
getEventRegistry().registerGlobal(PlayerReadyEvent.class, this::onPlayerReady);
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, event -> {
loadData();
});
// 5. Assety
getAssetRegistry().registerAssetMap(MyAsset.class, myAssetMap);
getLogger().atInfo().log("Setup complete");
}
Příklady z Dekompilovaného Kódu
#### TeleportPlugin
@Override
protected void setup() {
instance = this;
CommandRegistry commandRegistry = this.getCommandRegistry();
EventRegistry eventRegistry = this.getEventRegistry(); // Příkazy
commandRegistry.registerCommand(new TeleportCommand());
commandRegistry.registerCommand(new WarpCommand());
commandRegistry.registerCommand(new SpawnCommand());
// Eventy
eventRegistry.register(LoadedAssetsEvent.class, ModelAsset.class, this::onModelAssetChange);
eventRegistry.registerGlobal(ChunkPreLoadProcessEvent.class, this::onChunkPreLoadProcess);
eventRegistry.registerGlobal(AddWorldEvent.class, event ->
event.getWorld().getWorldMapManager().addMarkerProvider("warps", WarpMarkerProvider.INSTANCE)
);
eventRegistry.registerGlobal(AllWorldsLoadedEvent.class, event -> this.loadWarps());
// Komponenty
this.teleportHistoryComponentType = EntityStore.REGISTRY.registerComponent(
TeleportHistory.class, TeleportHistory::new
);
this.warpComponentType = EntityStore.REGISTRY.registerComponent(
WarpComponent.class, () -> {
throw new UnsupportedOperationException("WarpComponent must be created manually");
}
);
}
#### NPCPlugin
@Override
protected void setup() {
instance = this; // Registrace komponent
this.roleComponentType = getEntityStoreRegistry().registerComponent(
RoleComponent.class, RoleComponent::new
);
// Systémy
getEntityStoreRegistry().registerSystem(new AISystem());
getEntityStoreRegistry().registerSystem(new PathfindingSystem());
// Příkazy
getCommandRegistry().registerCommand(new NPCSpawnCommand());
// Eventy
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, event -> {
loadNPCDefinitions();
});
}
---
Fáze 4: start()
Kdy je Voláno
Po setup() a načtení světů. Plugin je plně aktivní.
Z Dekompilovaného Kódu
protected void start0() {
if (this.state != PluginState.SETUP) {
throw new IllegalArgumentException(String.valueOf(this.state));
} this.state = PluginState.START;
try {
this.start();
this.state = PluginState.ENABLED; // Úspěch → ENABLED
} catch (Throwable var2) {
this.logger.at(Level.SEVERE).withCause(var2).log("Failed to start %s", this.identifier);
this.state = PluginState.DISABLED; // Chyba → DISABLED
}
}
Co Dělat
@Override
protected void start() {
getLogger().atInfo().log("MyPlugin started!"); // Verifikace
if (config.get() == null) {
getLogger().atWarning().log("Config not loaded!");
}
// Logování statistik
getLogger().atInfo().log("Loaded %d items", loadedItems.size());
// Registrace asset packu (pokud manifest.includesAssetPack())
// Děje se automaticky v JavaPlugin.start0()
}
Automatické Zpracování Asset Packu
// Z JavaPlugin.start0()
@Override
protected void start0() {
super.start0(); if (this.getManifest().includesAssetPack()) {
AssetModule assetModule = AssetModule.get();
String id = new PluginIdentifier(this.getManifest()).toString();
AssetPack existing = assetModule.getAssetPack(id);
if (existing != null) {
this.getLogger().at(Level.WARNING).log(
"Asset pack %s already exists, skipping embedded pack", id
);
return;
}
assetModule.registerPack(id, this.file, this.getManifest());
}
}
---
Fáze 5: shutdown()
Kdy je Voláno
Při vypínání serveru nebo deaktivaci pluginu.
Z Dekompilovaného Kódu
protected void shutdown0(boolean shutdown) {
this.state = PluginState.SHUTDOWN; try {
this.shutdown();
this.state = PluginState.DISABLED;
} catch (Throwable var3) {
this.logger.at(Level.SEVERE).withCause(var3).log("Failed to shutdown %s", this.identifier);
}
this.cleanup(shutdown);
}
Co Dělat
@Override
protected void shutdown() {
getLogger().atInfo().log("MyPlugin shutting down..."); // DŮLEŽITÉ: Synchronní uložení dat!
try {
saveAllData();
} catch (Exception e) {
getLogger().at(Level.SEVERE).withCause(e).log("Failed to save data:");
}
// Zastavení tasků
cancelScheduledTasks();
// Cleanup resources
closeConnections();
getLogger().atInfo().log("MyPlugin shutdown complete");
}
---
Fáze 6: cleanup()
Automatický Cleanup
Po shutdown() se automaticky volá cleanup():
void cleanup(boolean shutdown) {
// Všechny registry se automaticky vyčistí
this.commandRegistry.shutdown();
this.eventRegistry.shutdown();
this.clientFeatureRegistry.shutdown();
this.blockStateRegistry.shutdown();
this.taskRegistry.shutdown();
this.entityStoreRegistry.shutdown();
this.chunkStoreRegistry.shutdown();
this.codecMapRegistries.forEach((k, v) -> v.shutdown());
this.assetRegistry.shutdown(); // Vlastní shutdown tasky (registrované přes shutdownTasks)
for (int i = this.shutdownTasks.size() - 1; i >= 0; i--) {
this.shutdownTasks.get(i).accept(shutdown);
}
}
Registrace Shutdown Tasků
// Shutdown tasky jsou automaticky volány při cleanup
// Můžeš je registrovat přes CopyOnWriteArrayList
---
AllWorldsLoadedEvent
Speciální Event
Volá se mezi setup() a start(), když jsou všechny světy načtené.
@Override
protected void setup() {
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, event -> {
// Všechny světy jsou načtené
// Můžeš bezpečně přistupovat k Universe.get().getWorld(...) loadWarps();
loadPlayerData();
initializeWorldSpecificData();
});
}
Proč Používat
// ❌ ŠPATNĚ - světy nemusí být načtené v setup()
@Override
protected void setup() {
World world = Universe.get().getWorld("overworld"); // Může být null!
}// ✅ SPRÁVNĚ - čekej na AllWorldsLoadedEvent
@Override
protected void setup() {
getEventRegistry().registerGlobal(AllWorldsLoadedEvent.class, event -> {
World world = Universe.get().getWorld("overworld"); // Garantovaně existuje
initializeWorld(world);
});
}
---
Kontrola Stavu Pluginu
Metody
// Je plugin aktivní?
public boolean isEnabled() {
return !this.isDisabled();
}// Je plugin vypnutý?
public boolean isDisabled() {
return this.state == PluginState.NONE
|| this.state == PluginState.DISABLED
|| this.state == PluginState.SHUTDOWN;
}
// Získej aktuální stav
public PluginState getState() {
return this.state;
}
Použití
public void doSomething() {
if (MyPlugin.get().isDisabled()) {
return; // Plugin není aktivní
} // Bezpečné provádět operace
}
---
Shrnutí
| Fáze | State | Metoda | Co dělat |
|------|-------|--------|----------|
| 1 | NONE | Konstruktor | super(init), withConfig() |
| 2 | NONE | preLoad() | Async načítání config |
| 3 | SETUP | setup() | Registrace komponent, systémů, příkazů, eventů |
| 4 | START→ENABLED | start() | Logování, verifikace |
| 5 | SHUTDOWN | shutdown() | Sync uložení dat, cleanup |
| 6 | DISABLED | cleanup() | Automaticky - registry cleanup |
| Event | Kdy | Použití |
|-------|-----|---------|
| AllWorldsLoadedEvent | Po setup(), před start() | Načtení dat závislých na světech |
| AddWorldEvent | Při přidání světa | Inicializace per-world dat |
| PlayerReadyEvent | Hráč vstoupil | Načtení hráčských dat |