Zum Hauptinhalt springen

MagmaCore-Entwickleranleitung

Die generierte Java-Referenz enthält durchsuchbare Klassen, Methoden und Signaturen sowie die Quellversion und den Commit jedes Moduls.

MagmaCore ist eine Bibliothek, die per Shading in die verwendenden Plugins eingebettet wird. Sie ist kein eigenständiges Server-Plugin und sollte nicht in depend stehen, als könnten Spieler eine MagmaCore-Plugin-JAR installieren.

Abhängigkeit und Verpackung​

Die aktuelle Entwicklungskoordinate lautet com.magmaguy:MagmaCore:2.2.0-SNAPSHOT. Sie wird in MagmaGuys Release-Repository veröffentlicht, nicht im Snapshot-Repository, das nur ältere 1.x-Snapshots enthält. Snapshots können sich ändern, ohne dass sich diese Versionszeichenfolge ändert, und der veröffentlichte Build wird nicht mit jedem Plugin-Release aktualisiert. Er kann daher älter sein als das MagmaCore, das in aktuelle Plugins eingebettet ist. Der am 5. Oktober 2026 veröffentlichte Build enthält die unten beschriebenen APIs für Updates ohne Neustart und Matches. Um gegen neuere, noch nicht veröffentlichte Änderungen zu kompilieren, baue MagmaCore aus dem Quellcode und veröffentliche es in Maven Local, wie unter Verbraucher nach Bibliotheksänderungen bauen beschrieben. Halte das aufgelöste Artefakt fest, wenn du reproduzierbare Builds benötigst.

Gradle Kotlin DSL
repositories {
maven("https://repo.magmaguy.com/releases")
}

dependencies {
implementation("com.magmaguy:MagmaCore:2.2.0-SNAPSHOT")
}
Maven sections
<repositories>
<repository>
<id>magmaguy-releases</id>
<url>https://repo.magmaguy.com/releases</url>
</repository>
</repositories>

<dependencies>
<dependency>
<groupId>com.magmaguy</groupId>
<artifactId>MagmaCore</artifactId>
<version>2.2.0-SNAPSHOT</version>
</dependency>
</dependencies>

Diese Deklarationen lösen die Abhängigkeit auf. Konfiguriere die Shading-Aufgabe deines Builds so, dass sie in der endgültigen Plugin-JAR enthalten ist. Folge den Relocation-Regeln eines bestehenden Verbrauchers mit denselben Integrationsanforderungen und prüfe bei Paketumbenennungen reflektive Zugriffe über Zeichenfolgen. Die Distribution verschiebt org.luaj, org.reflections und javassist nach com.magmaguy.shaded und lässt die nur zur Kompilierzeit benötigten javax.annotation-Klassen weg.

Jedes Plugin kann eine eigene geladene Kopie der Bibliothek haben. Java-Klassen gleichen Namens aus verschiedenen Plugin-Classloadern sind unterschiedliche Typen. Verwende die ausdrücklichen pluginübergreifenden Verträge, etwa den Dienst für Lua-Fähigkeiten von EliteMobs und die Adapter der Standort-Registry. Caste ein privates MagmaCore-Objekt eines anderen Plugins nicht auf den Typ deiner eigenen Kopie.

Module und Zuständigkeiten​

ModulZuständigkeit
corePlugin-Lebenszyklus, Konfiguration, Befehle, Menüs, Matches, Standortabfragen, Lua-Skripting, Nightbreak-Inhalte und gemeinsame Hilfsfunktionen
nms:coreNMSManager, NMSAdapter und die versionsunabhängigen EasyMinecraftGoals-Schnittstellen
nms:v*Implementierungen des NMS-Vertrags für die jeweilige Serverversion
distDistribution mit eingebettetem Core und aktiven Adaptern

Gemeinsame Verzauberungen, Item-Aktionen, Kontoverwaltung, Einrichtungsmenüs, Kataloge und Updates gehören zu den Core-Diensten. Verwende die zuständige Komponente wieder, wenn du einem Verbraucher Verhalten hinzufügst.

Plugin-Lebenszyklus​

Der minimale Core-Lebenszyklus folgt dem Muster bestehender Verbraucher:

CoreConsumer.java
package example;

import com.magmaguy.magmacore.MagmaCore;
import org.bukkit.plugin.java.JavaPlugin;

public final class CoreConsumer extends JavaPlugin {
@Override
public void onLoad() {
MagmaCore.createInstance(this);
}

@Override
public void onEnable() {
MagmaCore.onEnable(this);
}

@Override
public void onDisable() {
MagmaCore.shutdown(this);
}
}

Diese Initialisierung bewirkt mehr als das Bereitstellen eines Hilfsobjekts. Sie initialisiert Kontoverwaltung, gemeinsame Befehle, Listener und Unterstützung für temporäre Blöcke. Die Überladung createInstance(plugin, NightbreakPluginSpec) liefert pluginspezifische Texte für die gemeinsamen Befehle.

Bevorzuge Methoden, die das zuständige Plugin ausdrücklich entgegennehmen. getInstance() bezieht sich auf das Singleton der geladenen Bibliothekskopie, dessen anforderndes Plugin beim Erstellen dieser Instanz festgelegt wird. Daraus sollte nicht die Zuständigkeit für jeden Verbraucher abgeleitet werden.

shutdown(plugin) entfernt die Registrierungen dieses Plugins für Downloader, Inhaltsaktualisierung, Initialisierung und Updater und fährt anschließend auch die gemeinsame Laufzeit der geladenen Kopie herunter. Es entfernt also nicht nur einen Registry-Eintrag. Rufe es beim Herunterfahren deines Plugins für deine eigene eingebettete Bibliothek auf. Fahre niemals die Bibliothek eines anderen Plugins herunter, um deine Integration zurückzusetzen.

Rufe createInstance in onLoad auf, damit das Plugin als Eigentümer seines Konfigurationsarchivs registriert ist, bevor ein anderes Plugin Inhalte dafür importieren kann. Die Archivierung verwendet die im Ziel-Plugin eingebettete Implementierung. Wende dessen Regeln für ausgemusterte Konfigurationen nicht mit deiner eigenen MagmaCore-Kopie an.

Für einen Start in mehreren Phasen nimmt startInitialization asynchrone Arbeit, synchrone Arbeit sowie Erfolgs- und Fehler-Callbacks entgegen. Vor der asynchronen Initialisierung archiviert es Konfigurationsdateien, die einem ausgemusterten Format entsprechen. Bukkit-Arbeit an Welten und Entitäten gehört in die synchrone Phase. Lang laufende asynchrone Arbeit sollte zwischen den Phasen isShutdownRequested(plugin) beachten. getInitializationState und isPluginReady stellen den Zustand des Initialisierungsmanagers bereit.

Plugin-Updates ohne Neustart​

NightbreakPluginBootstrap.registerStandardCommands registriert applypluginupdate zusammen mit den anderen gemeinsamen Unterbefehlen. Der Befehl spielt ein heruntergeladenes Update ein, indem er dein Plugin deaktiviert, seine JAR ersetzt und es in derselben Serversitzung wieder aktiviert. onDisable muss daher Aufgaben abbrechen, Listener abmelden und jede Referenz freigeben, die den alten Classloader am Leben halten würde. Aktivierte Plugins, deren Hauptklasse unter com.magmaguy. liegt und die per depend oder softdepend von deinem abhängen, werden mit neu geladen. Jedes andere Plugin, das von deinem abhängt, lässt den Austausch scheitern und verlangt einen Neustart. Die Seite des Serverbetreibers ist unter Updates ohne Neustart anwenden beschrieben.

Kann ein Teil deines Plugins nicht an Ort und Stelle neu geladen werden, rufe NightbreakPluginHotSwap.requireRestartForUpdates(plugin, reason) auf, bevor die Update-Prüfungen starten, und übergib false als zweites Argument von NightbreakPluginUpdater.setAutoDownloadConfigDefault, damit config.yml applyPluginUpdatesWithoutRestart nicht anbietet. Das Plugin wird trotzdem mit seiner aktuellen JAR neu geladen, wenn ein Plugin, von dem es abhängt, ausgetauscht wird. NightbreakPluginUpdater.onPluginUpdateDownloaded(plugin, listener) führt einen Listener im Download-Thread aus, nachdem ein validiertes Update den Ordner update erreicht hat.

NMS und Paket-Entitäten​

Initialisiere NMS nur, wenn dein Plugin es benötigt, und verwende dafür NMSManager.initializeAdapter(plugin) aus com.magmaguy.easyminecraftgoals. Prüfe NMSManager.isEnabled(), bevor du adapterabhängiges Verhalten verwendest. Ein Initialisierungsaufruf kann fehlschlagen oder eine nicht unterstützte Serverversion vorfinden.

Die Distribution enthält folgende Adapterfamilien:

AdapterMinecraft-Versionen
v1_21_R31.21.4
v1_21_R41.21.5
v1_21_R51.21.6, 1.21.7, 1.21.8
v1_21_R61.21.9, 1.21.10
v1_21_R7_spigot, v1_21_R7_paper1.21.11, getrennte Spigot-/Paper-Implementierungen
v2626.1 und die aktuelle 26.x-Adapterfamilie

Die Adapterauswahl ist keine pauschale Kompatibilitätsgarantie für zukünftige Server-Releases. Die Laufzeitauswahl in NMSManager bestimmt, ob der installierte Server einen Adapter laden kann. Versionen vor 1.21.4 liegen außerhalb des aktuellen Adaptersatzes.

EasyMinecraftGoals verwaltet rein paketbasierte Entitäten, ihre Sichtbarkeit und Interaktionsweiterleitung sowie Hilfen für Bewegung und Wegfindung. Sie sind keine gewöhnlichen Welt-Entitäten. Verwalte sie über ihre zuständige API und entferne die von deinem Verbraucher erstellten Objekte, solange der Adapter noch verfügbar ist.

Standort- und Schutzabfragen​

com.magmaguy.magmacore.location.LocationQueryRegistry stellt Dungeon- und Regionsschutzabfragen bereit, darunter isInAnyDungeon, isInAnyProtectedRegion und canBuild.

Registriere eigene Provider mit registerDungeonLocator oder registerProtectionProvider und melde denselben Provider ab, wenn sein Besitzer deaktiviert wird. Die Registry enthält Adapter für bestehende Plugins und erkennt pluginübergreifende Dungeon-Provider, ohne dass Verbraucher einen gemeinsamen MagmaCore-Classloader benötigen.

Verwende die zur Aktion passende Abfrage. Außerhalb eines Dungeons zu sein bedeutet nicht, an diesem Ort bauen zu dürfen. Führe Abfragen, die Bukkit oder Schutz-Plugins verwenden, im Serverthread aus.

Matches​

Der Match-Lebenszyklus befindet sich in com.magmaguy.magmacore.match. Rufe beim Aktivieren einmal MatchCore.enable(plugin) auf, erweitere dann Match, übergib dem Konstruktor ein MatchSettings aus MatchSettings.builder() und rufe open() auf, nachdem der Konstruktor deiner Unterklasse fertig ist. Die Spiellogik gehört in die geschützten Hooks wie onJoin, onStart, onDeath und onLeave. MagmaCore.shutdown(plugin) zerstört die verbleibenden Matches des Plugins.

Der Core löst selbst keine Bukkit-Events aus. Er ruft die in den Einstellungen gesetzte MatchApi auf, sodass dein Plugin entscheidet, welche Events es bereitstellt. Ein abbrechbarer Callback legt sein Veto gegen den Schritt ein, indem er false zurückgibt oder eine Ausnahme wirft. Die Kopie jedes Plugins verfolgt nur ihre eigenen Matches; eine Metadaten-Markierung am Spieler, magmacore_match, teilt anderen Kopien mit, dass ein Spieler bereits in einem Match ist.

MatchSettings.builder().permission(...) legt die Berechtigung fest, die ein Spieler zum Beitreten braucht. Überschreibe hasJoinPermission(player), um das pro Spieler zu entscheiden, zum Beispiel damit Teammitglieder auch ohne sie beitreten können. Ein Teilnehmer, der ausscheidet, weil er gestorben ist (LeaveReason.DIED), wird vor dem Hinausbefördern auf volle Gesundheit geheilt, sodass ein auf dem Weg nach draußen entfernter Bonus auf die maximale Gesundheit das volle Verhältnis beibehält. Bei anderen Abgängen während des Matches bleibt der Schaden erhalten, und Spieler, die beim Ende des Matches noch darin sind, werden geheilt. Beide Verhaltensweisen sind neuer als der am 5. Oktober 2026 veröffentlichte Build.

Die älteren Match-Klassen in com.magmaguy.magmacore.instance, einschließlich MatchInstance, wurden entfernt. InstanceProtector bleibt dort erhalten und wird von MagmaCore.enableWorldProtections(plugin) registriert.

Lua-Mind-Hosts​

MagmaCore kompiliert in Lua geschriebene Mind-Programme für Host-Plugins. Das Skriptformat ist unter Verhaltensprogramme beschrieben. Ein Host erstellt seine eigene LuaMindModuleRegistry, öffnet mit openOwner(namespace) einen Besitzer für seinen Namensraum, registriert Module mit registerModule des Besitzers und erhält über validateProgram des Besitzers eine LuaMindDefinition. Jeder instantiate-Aufruf auf der Definition baut ein neues Programm mit eigener Lua-Umgebung für eine Entität. Die folgenden Hooks sind neuer als der am 5. Oktober 2026 veröffentlichte Build.

  • Ein Modul untersuchen. LuaMindModuleRegistry.inspectModule(sourceName, source) liest ID, Revision, Abhängigkeiten und die settings-Standardwerte eines Moduls, ohne es zu registrieren. LuaMindModuleDescriptor.settings() enthält Werte vom Typ Double, Boolean, String oder List<String>. MagmaCore reicht Einstellungen nicht an Skripte weiter. Ein Host, der Werte pro Kreatur will, legt seine eigenen über diese Standardwerte und stellt sie bereit, wie es FreeMinecraftModels-Haustiere mit c.settings tun.
  • Callback-Kontexte erweitern. LuaMindDefinition.instantiate(LuaMindContextExtension) fügt deine Felder zur Kontexttabelle jedes Callbacks hinzu. extend(table, context, contribution) erhält die Tabelle, bevor Lua sie sieht, den MindContext des Callbacks und die ID des Moduls, das den Callback deklariert hat, oder die eigene ID des Programms. Es läuft im Serverthread innerhalb des Ausführungsbudgets des Callbacks, seine CPU-Zeit zählt also zum cpu_micros des Programms, und eine Ausnahme lässt den Callback fehlschlagen. Füge Felder mit rawset oder mit LuaTableSupport.lazyField hinzu, das das vorhandene __index der Tabelle für jeden anderen Schlüssel beibehält. Ersetze weder die Metatable der Tabelle noch ihr __index, weil die eingebauten Abschnitte darüber gelesen werden, und überschreibe nicht tick, generation, entity, memory, perception, actuator oder actions. Das Lesen eines eingebauten Abschnitts aus extend baut ihn für diesen Callback auf. Übergib jedem instantiate-Aufruf eine eigene Erweiterung, wenn sie Zustand pro Entität hält.
  • Probe-Intervall. Ein Java-Verhalten kann MindBehavior.probeInterval() überschreiben, das Gegenstück zu probe_interval in Lua, um canStart nur alle N Ticks zu fragen, solange es gestoppt ist. Der Wert muss zwischen 1 und MindBehavior.MAX_PROBE_INTERVAL (200) liegen; MindProgram lehnt andere Werte ab. Der Standard 1 fragt in jedem Tick. Ein MindSensor sensiert im ersten Tick, in dem sein Körper getickt wird, und danach alle intervalTicks() Ticks auf einer Phase pro Entität.
  • Flug. MindActuator.steerFlight(Vector), das schon in diesem Build war, lässt einen FLYING-Körper für einen Tick mit dieser Geschwindigkeit fliegen und braucht die MOVE- und LOOK-Leases. Neu sind seine Obergrenze und das Lua-steer_flight: MindActuator.MAX_FLIGHT_SPEED ist 4 Blöcke pro Tick, und eine schnellere Geschwindigkeit wird darauf herunterskaliert. Auch RouteFlight begrenzt seine Geschwindigkeit dort, eine Route mit mehr als 80 Blöcken pro Sekunde fliegt also mit 80.

Verbraucher nach Bibliotheksänderungen bauen​

Verwende JDK 21 und veröffentliche MagmaCore mit ./gradlew publishToMavenLocal beziehungsweise unter Windows mit .\gradlew.bat publishToMavenLocal in Maven Local. Stelle sicher, dass der Verbraucher dieses Repository und die beabsichtigte Version auflöst, und baue dann seine JAR mit eingebetteten Abhängigkeiten neu. Das Ersetzen einer Bibliothek in Maven Local aktualisiert kein bereits gebautes Plugin.

APIs installierter Plugins findest du in der Entwicklerübersicht. Solche Integrationen verwenden normalerweise provided oder compileOnly; für die eingebettete MagmaCore-Bibliothek gelten andere Regeln.