Donnerstag, 7. Dezember 2023

JAVA Plugin Programmierung für Minefort Server

Im Artikel 'Einen Mincraft Server aufsetzen' habe ich mich bereits einmal mit Minecraft und der Programmierung von Plugins beschäftigt. Ich habe damals einen lokalen CraftBukkit Server aufgesetzt, und die relativ leicht zu erlernende Programmiersprache Python für die Plugins benutzt. Da Minecraft jedoch in JAVA programmiert ist, musste ich den 'Python PlugIn Loader' PPL und einen Laufzeitinterpreter wie Jyton verwenden um die Python Programme zum Laufen zu bringen.
Auf professionell gehosteten Servern wie Minefort wird dieses Vorgehen nicht unterstützt, aber normalerweise lassen sich native JAVA plugins in das System einbinden.
Die 'HUT' Version von Minefort für bis zu 20 Spieler und 1GB RAM wird  kostenlos angeboten, und ein eigener Server ist schnell erstellt. Untertützung findet man auch im recht ausführlichen blog. Das dashboard ist übersichtlich gestaltet,


und mit einem FTP Client wie FileZilla lassen sich die Plugins in die Verzeichnisstruktur des Servers laden.


Als JAVA Entwicklungsumgebung benutze ich IntelliJ IDEA, denn  IntelliJ verfügt über ein 'Minecraft Development' plugin, mit dessen Hilfe sich die Programmierarbeit deutlich vereinfacht. 


Als lokalen Testserver benutze ich diesmal PAPER, denn diese Spigot Variante läuft wesentlich stabiler als der von Getbukkit  angebotene Server.
Nach dem Download in einen Ordner, habe ich die *.jar Datei in 'paper.jar' umbenannt, und anschließend eine 'start.bat' Datei mit dem Inhalt

    java -jar paper.jar nogui

erstellt. Das erste Ausführen der 'start.bat' erzeugt unter Anderem das End User License Agreement 'eula.text'. In der Datei muß 'set eula=false' in 'true' geändert werden. Anschließend startet der Server korrekt.

Der Minecraft Java Client kann dann wie üblich im Minecraft Launcher gestartet werden. Möchte man sich auf dem Testserver einwählen, verbindet man sich sich im Multiplayer Modus mit 'localhost'. Möchte man sich auf Minefort einwählen, verbindet man sich mit dem entsprechenden host Namen oder der zugehörigen IP Adresse.

Um nun in IntelliJ ein Plugin zu schreiben, muss zunächst ein neues Projekt erstellt werden.


'Group ID' und 'PlugIn Name' müssen eingetippt werden, der Rest wird automatisch ergänzt, oder kann übernommen werden. Nun steht das Skelett einer Datei mit Namen 'TestPlugin.java' zur Verfügung.
Zunächst möchte ich nur Statusmeldungen beim Laden bzw. Stoppen des Servers ausgeben. Dazu definiere  ich zunächst einen String mit dem Namen 'Prefix' im body der 'TestPlugin.java'

    public static String Prefix ="§aMein Test §7§o";

Der Formatierungscode '§a' bedeutet in Minecraft Grün, '§7' Grau und '§0' Kursiv.

Die Methode 'log' soll dann den Prefix und den übergebenen Text ausgeben
 
    public void log(String text){
        Bukkit.getConsoleSender(). sendMessage(Prefix+text); }

In der bereits vorhandenen Methode 'onEnable()', die beim Hochfahren des Severs ausgefürt wird, ergänzen wir

    log("Plugin ist geladen");

und in der Methode onDisable()', die beim Herunterfahren ausgeführt wird, ergänzen wir

    log("Plugin ist entladen.");

Das gesamte Programm sieht dann wie folgt aus:

package de.myself.testplugin;

import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public final class TestPlugin extends JavaPlugin {

    public static String Prefix ="§aMein Test §7§o";

    public void log(String text){
        Bukkit.getConsoleSender(). sendMessage(Prefix+text);
    }

    @Override
    public void onEnable() {
        // Plugin startup logic
        log("Plugin ist geladen");

    }

    @Override
    public void onDisable() {
        // Plugin shutdown logic
        log("Plugin ist entladen.");
    }

}

Anschließend muss das Programm gepackt werden. Ein Klick auf das stilisierte 'm' öffnet das build-Werkzeug 'Maven'.


Ein Doppelklick auf 'package' erzeugt die Datei 'TestPlugin-1.0-SNAPSHOT.jar'. Diese kann vom  angegebenen Ort, oder direkt aus IntelliJ kopiert werden, und in den 'plugin' Ordner des Servers gelegt werden.


Beim Hochfahren des Servers erscheint dann das Folgende:


Die Konsole auf Minefort kann leider nur graue Farbe ausgeben. Auf dem Testserver erscheinen die Meldungen hingegen farbig wie gewünscht.

Um ein neues Kommando zu programmieren, das aus Minecraft heraus aufgerufen werden kann, müssen wir eine neue Klasse erstellen. In Java wird eine Klasse für neue Variablen und Methoden allgemein durch

    <modifier> class 'Name' <superclass> <(interface1; interface2;...)> {
    //variables
    //methods
    }

definiert. 'Modifier' regelt dabei den Zugriff, und steht dabei z.B. für 'public'. Im 'body', zwischen den Klammern, stehen dann die neuen Methoden und Variablen.
Ich möchte einen Befehl programmieren, der alle Leben bzw. Herzen und die Essensvorräte wieder auffüllt. Wir erstellen uns zunächst ein neues package namens 'commands', und anschließend die neue Klasse 'HealCommand'. 


Es gilt die Konvention, dass package Namen immer klein geschrieben werden, und Klassennamen in CamelCase, d.h. zumindest mit einem Großbuchstaben beginnen. ItelliJ gibt uns den allgemeinen Rahmen bereits vor, und hilft wie unten gezeigt beim Einfügen.


Um die Klasse später als Minecraft Kommando registrieren zu können, muss die neue Klasse die Interfaces der superclass 'CommandExecutor' erben. Dies geschieht durch Eingabe von 'implements CommandExecutor' nach dem Klassennamen. Die Methode 'onCommand()' ist bei Verwendung der ererbten Interfaces verpflichtend.


'@Override' bedeutet dabei, dass wir die in CommandExecutor bereits definierte Methode überschreiben. 'commandSender' beinhaltet wer den Befehl sendet, 'command' den Befehl, hier also '/heal', 's' die etwaigen 'alias label', und 'strings' die Liste der zusätzlichen Argumente für das Kommando.

Insgesamt sieht der Code wie folgt aus:

package de.myself.testplugin.commands;

import de.myself.testplugin.TestPlugin;
import org.bukkit.Sound;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

public class HealCommand implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {
        if (!(commandSender instanceof Player)){
            TestPlugin.INSTANCE.log("Du bist kein Spieler");
            return true;
        }
        Player player = (Player) commandSender;
        if (player.hasPermission("de.myself.testplugin.heal")){
            //Alle Herzen füllen
            player.setHealth(20d);
            //Essen auffüllen
            player.setFoodLevel(20);
            player.sendMessage(TestPlugin.Prefix+"Du wurdest geheilt!");
            player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 0.2f, 1.2f);
        } else {
            player.sendMessage(TestPlugin.Prefix + "Du hast keine Permissions");
            player.playSound(player.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.2f, 1.2f);
        }

        return true;
    }
}


Durch die Abfrage

    if (!(commandSender instanceof Player)){
            TestPlugin.INSTANCE.log("Du bist kein Spieler");
            return true;
    } 

wird sichergestellt, dass das Kommando von einem Spieler gesendet wurde, und nicht von der Konsole. Dann wird mit

Player player = (Player) commandSender;

der übergebene 'commandSender' in eine Variable namens 'player' vom Typ 'Player' ge(type)casted, und zunächst abgefragt, ob der Spieler die in der 'plugin.yml' festgelegten Berechtigungen besitzt um das Kommando auszuführen.
 
        if (player.hasPermission("de.myself.testplugin.heal")){
            //Alle Herzen füllen
            player.setHealth(20d);
            //Essen auffüllen
            player.setFoodLevel(20);
            player.sendMessage(TestPlugin.Prefix+"Du wurdest geheilt!");
            player.playSound(player.getLocation(), Sound.BLOCK_NOTE_BLOCK_PLING, 0.2f, 1.2f);
        } else {
            player.sendMessage(TestPlugin.Prefix + "Du hast keine Berechtigung zum Heilen!");
            player.playSound(player.getLocation(), Sound.ENTITY_GENERIC_EXPLODE, 0.2f, 1.2f);
        }

Besitzt der Spieler die Berechtigung werden Herzen und Essen aufgefüllt, und ein wohlklingendes Geräusche mit Lautstärke 0.2f und pitch 1.2f abgespielt. 
Der Rückgabevariable 'return' kann auf 'true gesetzt werden um die Ausgabe der 'usage' in der 'pom.xml' zu verhindern.

Anschließend muss mit

     private void register(){
        Bukkit.getPluginCommand("heal").setExecutor(new HealCommand());
    }

der Befehl "heal" in der 'TestPlugIn' registriert werden. Damit die Registrierung beim Hochfahren des Servers erfolgt, erzeugen wir mit 

    public TestPlugin(){
        INSTANCE=this;
    }

eine Instanz, d.h. ein wertetragendes Objekt der 'TestPlugin' Klasse, namens 'this' und führen 'this.register()' in 'onEnable()' beim Hochfahren des Servers aus.

    public void onEnable() {
        // Plugin startup logic
        log("Plugin ist geladen");
        this.register();
    }

Die modifizierte 'TestPlugin' sieht dann wie folgt aus:
  
package de.myself.testplugin;

import de.myself.testplugin.commands.HealCommand;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;

public final class TestPlugin extends JavaPlugin {

    public static String Prefix ="§aMein Test §7§o";
    public static TestPlugin INSTANCE;

    public TestPlugin(){
        INSTANCE=this;
    }

    public void log(String text){
        Bukkit.getConsoleSender(). sendMessage(Prefix+text);
    }

    @Override
    public void onEnable() {
        // Plugin startup logic
        log("Plugin ist geladen");
        this.register();

    }

    @Override
    public void onDisable() {
        // Plugin shutdown logic
        log("Plugin ist entladen.");
    }

    private void register(){
        Bukkit.getPluginCommand("heal").setExecutor(new HealCommand());
    }
}

Zu guter Letzt muss noch in der Manifestdatei des plugins mit dem Namen 'plugin.yml' das Kommando beschrieben werden, und es können alternative Befehlsnamen, sogenannte 'aliases' definiert werden. Wir ergänzen:

    commands:
      heal:
        description: Dieser Befehl heilt dich
        aliases: ["heilmich"]

und geben dem Operator Spieler 'op' Zugriff auf das '\heal' Kommando

permissions:
  de.myself.testplugin.heal:
    default: op

Insgesamt sieht die 'plugin.yml' dann so aus:

name: TestPlugin
version: '${project.version}'
main: de.myself.testplugin.TestPlugin
api-version: '1.20'

commands:
  heal:
    description: Dieser Befehl heilt dich
    aliases: ["heilmich"]

permissions:
  de.myself.testplugin.heal:
    default: op


Im terminal Fenster des Servers kann jeder Spieler später als 'op' gesetzt werden.
Zum Abschluss möchte ich den '/heal' Befehl noch in Aktion zeigen. 
Bildschirmaufzeichnungen lassen sich unter Windows 10 übrigens recht einfach mit 'Windowstaste' + 'G' starten. Die Aufzeichnung kann dann z.B. mit dem Windows Movie Maker bearbeitet werden.


Viel Spaß beim selber Programmieren...

Keine Kommentare:

Kommentar veröffentlichen