HyCodeYourTale

Plugin Phases

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 |

Last updated: 20. ledna 2026