Minecraft Plugins: Das Spigot-Framework

Aus Jonas Notizen Webseite
Zur Navigation springen Zur Suche springen

Inhaltsverzeichnis

Einleitung

Spigot ist ein Framework zum erstellen von "Plugins" für Server:

Ein Plugin kann keine neue Items/Blöcke hinzufügen, weil dies eine Modifikation wäre von Sachen die der Client nicht implementiert hat.

Ein Plugin kann:

  • neue Befehle/Crafting-Rezepte hinzufügen
  • Aktionen bei gewissen Geschehnissen (Events) ausführen, dessen Werte verändern oder gar das Event aufhalten
  • Packete abfangen/senden (Beispiel: eigene NPCs)
  • so manch Einstellungen am Server vornehmen (MOTD, Whitelist, Tab-Anzeige, ...).

Dies klingt zwar erst wenig, aber wenn man sich mal anschaut was man alles mit dem Spieler/Welt/Items/Inventaren/... machen kann, ist es ein großes System.

Auf YouTube existieren viele "Minecraft Plugins programmieren"-Videoreihen von Menschen, die meißt keine große Ahnung von der Programmiersprache Java an sich haben (und deswegen auch meist schlechte Prinzipien beibringen), sowie sagen "Das muss man so machen", oder halt nichts genauer erklären und damit dann viele unnötige Fragen in manchen Foren/Discord-Servern/... produzieren.

Diese Seite habe ich hauptsächlich für Freunde gemacht, oder falls ich wieder eine große Pause von Spigot mache (Um mich wieder an die Prinzipien des Frameworks zu erinnern).

Links






Projekt aufsetzen (IntelliJ)

Spigot.jar einfügen

Um das Spigot-Framework nutzen zu können, müssen wir es zuerst in unsere Dependencies einfügen.

  1. Downloade deine gewünschte Version von der GetSpigot-Webseite.
  2. Öffne dein IntelliJ-Projekt
    1. ["File" (Oben Links) > "Project Structure"] oder [Strg+Alt+Umschalt+S]
    2. Sektion ["Libraries"]
    3. ["+" > "Java"] > gedownloadete .jar-Datei auswählen > ["OK"]
    4. ["OK"] oder ["APPLY"] (unten Rechts) zum speichern.

Artifacts erstellen

Um aus unseren Projekt-Sourcecode eine .jar-Datei zu machen, müssen wir sog. "Artifakte" zum exportieren erstellen.
Danach kümmert sich IntelliJ mit 3 einfachen Knopf-drücken um die Umwandlung des Projekts in ein Java-Archiv (.jar), das unser Plugin darstellt.

  1. Öffne dein IntelliJ-Projekt
    1. ["File" (Oben Links) > "Project Structure"] oder [Strg+Alt+Umschalt+S]
    2. Sektion ["Artifacts"]
    3. ["+" > "JAR" > "From Modules with dependencies"] > ["OK"]

Nun kannst du bei "Output Directory" den Pfad angeben, wo deine .jar-Datei gespeichert werden soll. (zB. in den /plugins-Ordner deines Minecraft Servers)

Artifacts bauen

Wenn man seinen Quellcode nun in eine .jar-Archiv kompilieren will, damit es der Server nutzen kann, muss man nurnoch:

  1. ["Build" (Oben Links) > "Build Artifacts"] [(Dein Projekt) > "Build"]

und die Datei (Falls noch nicht getan) in den /plugins-Ordner seines Servers schieben.

Um das Plugin neu zu laden muss man nurnoch einmal /reload als Administrator des Servers eingeben.


Projekt aufsetzen (IntelliJ)

Spigot.jar einfügen

Um das Spigot-Framework nutzen zu können, müssen wir es zuerst in unsere Dependencies einfügen.

  1. Downloade deine gewünschte Version von der GetSpigot-Webseite.
  2. Öffne dein IntelliJ-Projekt
    1. ["File" (Oben Links) > "Project Structure"] oder [Strg+Alt+Umschalt+S]
    2. Sektion ["Libraries"]
    3. ["+" > "Java"] > gedownloadete .jar-Datei auswählen > ["OK"]
    4. ["OK"] oder ["APPLY"] (unten Rechts) zum speichern.

Artifacts erstellen

Um aus unseren Projekt-Sourcecode eine .jar-Datei zu machen, müssen wir sog. "Artifakte" zum exportieren erstellen.
Danach kümmert sich IntelliJ mit 3 einfachen Knopf-drücken um die Umwandlung des Projekts in ein Java-Archiv (.jar), das unser Plugin darstellt.

  1. Öffne dein IntelliJ-Projekt
    1. ["File" (Oben Links) > "Project Structure"] oder [Strg+Alt+Umschalt+S]
    2. Sektion ["Artifacts"]
    3. ["+" > "JAR" > "From Modules with dependencies"] > ["OK"]

Nun kannst du bei "Output Directory" den Pfad angeben, wo deine .jar-Datei gespeichert werden soll. (zB. in den /plugins-Ordner deines Minecraft Servers)

Artifacts bauen

Wenn man seinen Quellcode nun in eine .jar-Archiv kompilieren will, damit es der Server nutzen kann, muss man nurnoch:

  1. ["Build" (Oben Links) > "Build Artifacts"] [(Dein Projekt) > "Build"]

und die Datei (Falls noch nicht getan) in den /plugins-Ordner seines Servers schieben.

Um das Plugin neu zu laden muss man nurnoch einmal /reload als Administrator des Servers eingeben.


Lokalen Minecraft Server unter Windows aufsetzen

Um seine Plugins direkt, lokal und live austesten zu können lohnt es sich, einen lokalen Minecraft Server einzurichten. Hier ein Tutorial für Windows-Nutzer:

  • Neuen Ordner mit beliebigen Namen an einen beliebigen Ort erstellen. (Hier werden alle Server-Dateien reinkommen)
  • Die gewünschte Spigot-Version von der GetSpigot-Webseite herunterladen und in den oben ganannten Ordner kopiern
  • Eine neue .bat-Datei mit beliebigen Namen (meißt start.bat) und folgendem Inhalt erstellen:
@echo off
java -Xms1G -Xmx1G -XX:+UseConcMarkSweepGC -jar spigot.jar
pause
  • Erstellte Batch-Datei mit einem Doppelklick ausführen
  • Ein Kommandozeilenfenster öffnet sich. Diese fordert einem beim aller-ersten Start des Servers auf, in der vom Server generierten eula.txt die Minecraft EULA zu akzeptieren (sprich: =false in der Textdatei durch =true ersetzen). Wenn dies gemacht ist können wir den Server wieder mit der Batch-Datei starten. Dieses mal sollte er normal hochfahren, die Welten sowie alle Dateien im Ordner generieren.

Der Server läuft jetzt auf eurem PC, und kann im Minecraft-Client mit der IP 127.0.0.1 oder localhost über den Standard-Minecraft-Port 25565 erreicht werden. (Port-Nummer kann in der server.properties-Datei individuell angepasst werden)

Siehe auch Eintrag auf Spigot zum Thema Server-Installation

Getting Started

Die plugin.yaml

Wenn Bukkit ein Plugin lädt, dann braucht es einige grundlegende Infos über dieses. Es liest die Infos aus einer YAML-Datei aus, die "plugin.yml".

Jedes Plugin besitzt eine Datei mit dem Namen plugin.yml, in der generelle Informationen über das Plugin geschrieben sind.

  • Diese Datei muss direkt im Projekt-Verzeichnis-Stammbaum liegen (Nicht src)!

Siehe https://bukkit.gamepedia.com/Plugin_YAML/de für mögliche Konfigurationsmöglichkeiten.

Beispiel:

# All diese Konfigurationswerte können im nach-hinein im Quellcode einfach ausgelesen werden
name: Pixel's Tolles Plugin
description: Beispiels YAML
version: 0.0.1
# Die Hauptklasse, welche von 'JavaPlugin' erbt
main: at.pixeltutorials.tollesplugin.TollesPlugin

# Befehle registrieren. (Welchen CommandExecutor sie aufruft definieren wir erst beim starten des Plugins im Quellcode)
commands:
   clearchat:
        aliases: ["cc"]

Die Hauptklasse

Jedes Plugin besitzt zudem eine Klasse, die das Plugin repräsentiert.
Diese muss

  • in der plugin.yml unter main: mit ihrem voll-qualifizierten Namen verlinkt werden,
  • von JavaPlugin erben und
  • einen leeren Konstruktor haben.

Am besten sollte man seine Klasse nach dem Namen seines Plugins benennen.
Wenn man diese nähmlich nur Main nennt, kann es zu Konflikten/Verwirrungen mit anderen Plugins kommen, deren Hauptklasse auch Main heißt.

Ihre 3 Haupt Observer-methoden, welche für uns interessant sind, sind:

  • onEnable(): Wird beim aktivieren des Plugins ausgeführt.
    • Hier sollten alle Command-Executors/Event-Listeners/Craftin-Rezepte des Plugins registriert werden.
  • onDisable(): Wird beim abschalten des Plugins ausgeführt.
    • Beim abschalten/neu-laden des Plugins gehen natürlich auch alle Java-Objekte/Variablen/... verloren.
      • Hier kriegt man nocheinmal die möglichkeit, diese Daten (in zB. einer Datei) zwischen-zu-speichern, um Sie beim starten wieder auszulesen und damit die Objekte/HashMaps/... "wiederherzustellen".
  • getDescription(): Gibt die Daten der plugin.yml als PluginDescriptionFile-Instanz zurück

Beispiel-Klasse:

package at.pixeltutorials.pixelspigot;

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

public class PixelSpigot extends JavaPlugin {

    @Override
    public void onDisable() {
        super.onDisable();
        Bukkit.getConsoleSender().sendMessage("§cFahre herunter!");
    }
    
    @Override
    public void onEnable() {
        super.onEnable();
        Bukkit.getConsoleSender().sendMessage("§aPlugin startet, intialisiere Befehle und Events...");
    }
}






Befehle

Befehl in der plugin.yml registrieren

Jeder Befehl muss zuerst in der plugin.yml unter der Listen-Sektion commands: eingetragen werden.
Beispiel:

commands:
   clearchat:
        aliases: ["cc"]
   heal:

CommandExecutor und TabCompleter

Nun kann man beim starten des Plugins einen dazugehörigen CommandExecutor/TabCompleter verlinken, der sich um das ausführen/die Auto-vervollständigung kümmert.

  • Meist erstellt man für jeden Befehl eine seperate Klasse, welche
    • den Namen des Befehls, sowie
    • einen Hinweis, dass es sich um einen Befehl handelt
  • im Namen trägt. Beispiel: "HealCMD"

(Nach dem Prinzip "Eine Klasse sollte 1en groben Zweck erfüllen"). Siehe Using a seperate CommandExecutor class


Hierfür gibt es 2 Funktionale Schnittstellen:

package org.bukkit.command;

public interface CommandExecutor {

    /**
     * Wird beim ausführen des registrierten Befehls ausgeführt.
     * @param sender Derjenige, der den Befehl ausgeführt hat 
     * @param command Die [https://hub.spigotmc.org/javadocs/spigot/org/bukkit/command/Command.html org.bukkit.command.Command]-Instanz mit Informationen über den Befehl
     * @param label Was für einen Alias/Befehls-Name zum ausführen genutzt wurde
     * @param args Mitgesendete Argumente ('/heal Hallo du da' -> ["Hallo", "du", "da"])
     * @return Ob "der Befehl Korrekt war". Wenn nicht, wird die "usage"-Nachricht des Befehls von der plugin.yml ausgegeben.
     */
    boolean onCommand(CommandSender sender, Command command, String label, String[] args);
}
package org.bukkit.command;

import java.util.List;

public interface TabCompleter {
    /**
     * Wird beim "Tab-drücken" ausgeführt für Autovervollständigung des derzeitigen Arguments.
     * Parameter sing gleich wie beim CommandExecutor (Siehe oben).
     * @return Eine Liste an Autovervollständigungs-Möglichkeiten für das derzeitige Argument
     */
    public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args);
}

Der CommandSender

Vielleicht ist der schon das CommandSender sender aufgefallen. CommandSender ist ein Bukkit Interface, für welches es (für Plugin-programmierer) 2 wichtige Sub-klassen gibt:

Sichere Befehle schreiben

Beim schreiben einer onCommand-Logik sollte man folgende Sachen im Kopf behalten:

CommandSender-Typ checken, bevor man castet

Sicher gehen, dass der CommandSender ein Spieler ist, bevor man das Objekt dazu castet.

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
    if (sender instanceof Player) {
        Player player = (Player) sender;
        // do something
    } else {
        sender.sendMessage("You must be a player!");
        return false;
    }
    // do something
    return false;
}

Argumenten-Länge checken

Man kann natürlich nicht verlangen, dass der Spieler immer das richtige eingibt.

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
    if (args.length > 4) {
        sender.sendMessage("Too many arguments!");
        return false;
    } else if (args.length < 2) {
        sender.sendMessage("Not enough arguments!");
        return false;
    } else {
        sender.sendMessage("Perfect arguments :D!");
        return true;
    }
}

Checken, ob der angegebene Spieler online ist

@Override
public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
    Player target = (Bukkit.getServer().getPlayer(args[0]));
    if (target == null) {
        sender.sendMessage(args[0] + " is not online!");
        return false;
    }
    return false;
}

Wenn man einen Spieler modifizieren will, der gerade nicht online ist, bietet die Klasse OfflinePlayer grundlegende Manipulaitons-Methoden an.

Beispiel-Befehl: Heal

Hier ist ein Beispiel für einen Befehl, der

  • sich selber heilt, wenn nur "/heal" von einem Spieler eingegeben wurde
  • Einen anderen Spieler heilt (falls angegeben mit "/heal <Name>")
public class HealCMD implements CommandExecutor {
    @Override
    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
        if (args.length == 0) {
            // '/heal'
            if (sender instanceof Player) {
                Player player = (Player) sender;
                player.setHealth(player.getHealthScale());
                sender.sendMessage("§aDu wurdest geheilt.");
            } else {
                sender.sendMessage("§cNur ein Spieler kann sich selber heilen.");
            }
        } else if (args.length == 1) {
            // '/heal <Spielername>'
            String inputName = args[0];
            Player target = Bukkit.getPlayer(inputName);
            if (target != null) {
                target.setHealth(target.getHealthScale());
                sender.sendMessage("§a" + target.getName() + " wurde geheilt!");
                target.sendMessage("§aDu wurdest von " + sender.getName() + " geheilt!");
            } else {
                sender.sendMessage("§cDer angegebene Spieler (" + inputName + ") ist nicht online.");
            }
        } else {
            sender.sendMessage("§cSyntax: /heal (Spielername>");
        }
        return true;
    }
}

CommandExecutor-Instanz beim starten eintragen

@Override
public void onEnable() {
    super.onEnable();
    Bukkit.getConsoleSender().sendMessage("§aPlugin startet, intialisiere Befehle und Events...");
    Bukkit.getPluginCommand("heal").setExecutor(new HelloCMD()); // Hiermit greift Spigot nun auf die 'onCommand'-Methode von 'HelloCMD' beim ausführen des Befehls zu
}

Befehl (an sich) in der plugin.yml eintragen:

commands:
   heal:






Events

Was ist ein Event?

Bei so ziemlich allem, was auf einem Server passieren kann wird ein Event abgefeuert.
Plugins können mithilfe von Listeners auf diese Events hören, Werte verändern oder generell mitgelieferte Informationen zum Event erhalten und Code ausführen.

Welche Events gibt es

Bukkit definiert eine große Anzahl an Events, eingeteilt in verschiedene Kategorien.
Darunter: (mit Verlinkung zu allen verfügbaren Events)

Meistens verrät eh schon allein der Name des Events, wann es aufgerufen wird.

Auf Events hören

Um auf Events zu hören, benötigt man zuerst eine Klasse welche von org.bukkit.event.Listener erbt.
Diese Klasse muss beim Starten des Plugins (onEnable()) registriert werden: getServer().getPluginManager().registerEvents(new DeineKlasse(), this);

Für jedes Event, auf welche diese Klasse hören will, braucht man eine Funktion mit der Annotation @EventHandler, sowie das gewünschte Event als einzigen Parameter dieser Funktion.

Beispiel

Eine Beispiels-Funktion, welches auf das PlayerJoinEvent hört und die Nachricht grün einfärbt.

import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;

public class MeinKrasserListener implements Listener {

    @EventHandler(priority = EventPriority.HIGHEST)
    public void onJoin(PlayerJoinEvent event){
        event.setJoinMessage("§a" + event.getJoinMessage());
    }
}

Alle möglichen Events, sowie deren Eigenschaften und wann sie aufgerufen werden, findet man in den Javadocs.

Eigenes Event erstellen

Referenz auf Englisch

Jedes Event muss von der Klasse Event erben. Zusätzlich braucht jede Event-Klasse noch die unten folgenden Eigenschaften/Methoden/Variablen.

import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;

public class ExampleEvent extends Event {
    
    public String test;
    
    // JEDES Event braucht exakt diese 2 Funktionen!
    private static final HandlerList HANDLERS = new HandlerList();
    public HandlerList getHandlers() {
        return HANDLERS;
    }
    public static HandlerList getHandlerList() {
        return HANDLERS;
    }

}

You need these methods because Spigot uses the HandlerList class to separate other EventHandlers from listening to other events.

Um das Event nun zu broadcasten, damit andere es verändern/darauf "hören" können, muss man nurnoch:

ExampleEvent exampleEvent = new ExampleEvent(); // Instanz für den Aufruf erstellen. (Diese kann durch ihren Broadcast von anderen Listenern auch verändert werden.)
Bukkit.getPluginManager().callEvent(exampleEvent); // Event broadcasten

// Nun, wo alle EventHandler fertig sind, können wir hier mit unserer (vllt. veränderten) Version des Events weiterarbeiten
Bukkit.getPlayer("PixelTutorials").sendMessage(exampleEvent.test);




Eigene Configs

Bukkit nutzt vorgezogen YAML zur Daten-(De)Serialisierung:

  • Die Simple-YAML API ist ein Auschnitt der YAML-API die von Bukkit benutzt wird

YAML is a human-readable data-oriented serialization language.

Serialization is the process of translating data structures or object state into a format that can be stored and reconstructed later in the same or another computer environment.

Beispiel

File configFile = new File("plugins/MeinKrassesPlugin", "Config.yml");
FileConfiguration config = YamlConfiguration.loadConfiguration(configFile);

config.set("path.to.something", 22);
// Wenn man etwas setzt, ist es erst nur im Java-Objekt (Cache) verändert. Um den Cache jetzt in die Datei zu schreiben, muss man nur noch:
config.save(configFile);

Alle Methoden für Config-Sektionen (Getters & Setters, wie zB. getList) findet man hier.
Hier sind Beispiele wie Simple-YAML funktioniert, und wie man seine eigene Java-Objekte mithilfe von YAML de/-serialisiert.





Scheduler

Derzeit betreiben Minecraft-Server fast die gesamte Spielelogik in einem Thread (Welcher sich an die '20-Ticks pro Sekunde' Rate von Minecraft bindet).

  • Daher sollte jede einzelne Aufgabe, die im Spiel ausgeführt wird, sehr kurz gehalten werden.
    • Ein komplizierter Code im Plugin (zB. mehrere Datenbank-Abfragen) kann zu erheblichen Verzögerungen der Spiellogik führen, wenn Sie nicht ordnungsgemäß behandelt werden.

Glücklicherweise unterstützt Bukkit das geplante ausführen von Code in Plugins. Mithilfe dieser kann man:

  • eine ausführbare Aufgabe einreichen, die
    • einmal in der Zukunft oder
    • auf wiederkehrender Basis ausgeführt werden soll.

Diese Aktionen kann man auch in einen komplett neuen, unabhängigen Thread laufen lassen, der parallel zur Spielelogik langwierige Aufgaben ausführen kann. (= Asynchron)

Jeder Scheduler hat eine ID, mit welcher er durch Bukkit.getScheduler().cancelTask(task.getTaskId()); von überall aus gestoppt werden kann.

BukkitScheduler

Die BukkitScheduler-Klasse enthält die Methoden, die zum starten eines Schedulers genutzt werden.

BukkitTask

Ein BukkitTask repräsnetiert die Aufgabe, die vom Scheduler ausgeführt wird.

Tipps für Thread-Sicherheit

Die Bukkit(-Scheduler)-API wurde nicht darauf konzipiert, 100%ig Thread-Sicher zu sein.
Bei Schedulern sollte man auf folgendes achten:

  • Asynchrone Aufgabe sollten nie auf die Bukkit-API zugreifen
    • Wenn Bukkit diesen Zugriffs-Fehler nicht bemerkt können gröbere Korruptionen zur Welt (oA.) auftreten.
    • Asynchrone-Aufgaben haben aus diesem Grund keine Wichtigkeit für Bukkit und können auch eingefroren werden.
  • Man sollte nicht auf Collections zugreifen, auf welche auch von anderen Threads gleichzeitig zugegriffen werden kann (shared-collections)
    • Normale Collections sind nicht Thread-Sicher.
    • Dies gilt auch für andere Objekte, die nicht Thread-Sicher sind.
  • Eine asynchrone Aufgabe kann synchrone Aufgaben ausführen
  • Eine synchrone Aufgabe kann asynchrone Aufgaben ausführen
  • Wenn man etwas zu einen fix-festgelegten Zeitpunkt auführen möchte, sollte man einen Asynchronen-Aufgabe bevorzugen, da Lag den Delay verschieben kann.

wiederholende Aufgaben

Führt den Task mit einem gewissen Delay fortlaufend aus, bis er gestoppt wird.

BukkitTask task = Bukkit.getScheduler().runTaskTimer(this, () -> {
    // run()-Methode vom Runnable
    System.out.println("Hallo!");
},
 20, // Startet nach 6 Sekunden
 20 * 6); // Wiederholt jede Sekunde

// Kann später durch sich selbst oder wen anders gestoppt werden:
// Bukkit.getScheduler().cancelTask(task.getTaskId());

normale Aufgabe

verzögerte Aufgaben

Das gleiche gibt es auch mit einem "Start-Delay" (in Ticks):

Items und Blöcke

Material

Material ist ein Enum von allen verfügbaren Blöcken/Items.

Ein Material besitzt die folgende Eigenschaften/Methoden:

ItemStack

Wenn wir im Code mit Items hantieren möchten, nutzen wir die Klasse ItemStack.

Neue ItemStacks erstellen

Die Klasse ItemStack besitzt folgende Konstruktore, welche zur Erstellung neuer Items genutzt werden:

Eigenschaften

Die ItemStack-Klasse an sich besitzt nur 4 grundlegende Eigenschaften, darunter:

  • Alle Verzauberungen,
  • die Anzahl an Items im Stack,
  • die maximale Stack-größe und
  • das Material

Jedes ItemStack besitzt folgende Eigenschaften/Methoden:

ItemMeta

Zusätzliche Metadaten, wie zB. der Anzeigename des Items, werden mithilfe des ItemMeta-Interfaces dargestellt.

Ein wichtiges Merkmal, was zu beachten ist:
ItemStack#getItemMeta() gibt nur eine Kopie der ItemMeta zurück!

  • Dass heißt, nur ItemStack#getItemMeta().setDisplayName("Hey"); auszuführen, wird nichts an den Metadaten des ItemStack-Objekts verändern.

Stattdesen muss man dem ItemStack unsere (modifizierte) Kopie der ItemMeta selber zuweisen:

ItemStack meinItem = new ItemStack(Material.STONE);
ItemMeta meineMeta = meinItem.getItemMeta();
meineMeta.setDisplayName("Hey"); // Da wir hier nur mit einer Kopie hantieren, hat unser "meinItem" noch nichts von der Veränderung mitbekommen
meinItem.setItemMeta(meineMeta); // Unserem Item die neuen (modifizierten) Metadaten zuweisen.

Eigenschaften:


Serializable

ItemStack erbt von ConfigurationSerializable und CloneAble.

  • Dies bedeuted, dass es ganz einfach mithilfe der get und set-Methode der YamlConfiguartion in eine Config geschrieben und von dieser wieder geladen werden kann.


Blöcke

Wenn wir im Code mit Blöcken hantieren möchten, nutzen wir die Klasse Block.

Blöcke verändern

Der einfachste Weg um einen Block zu erstellen ist es, einen existierenden Block zu kriegen und diesen zu modifizieren.

Um einen bestehenden Block zu bekommen, gibt es folgende Möglichkeiten:

Der folgende Code setzt den Block-Typen des Blocks, der 5 Blöcke über der Position des Spielers ist, auf Stein. (Und das jedes mal, wenn der Spieler sich auch nur ein bisschen bewegt)

@EventHandler
public void onPlayerMove(PlayerMoveEvent event) {
    // Kopie von der Location des Spielers bekommen.
    Location loc = event.getPlayer().getLocation();
    // Y-Achsenwert der Location 5 Blöcke erhöhen
    loc.setY(loc.getY() + 5);
    // Block an der neuen Location bekommen.
    Block b = loc.getBlock();
    // Block-Typ setzen.
    b.setType(Material.STONE);
}

Eigenschaften

TODO Methoden beschreiben Jedes Block besitzt folgende Eigenschaften/Methoden:

BlockData

Manche Materialien haben spezielle Eigenschaften, welcher nicht jeder Block haben kann.
Diese speziellen Metadaten werden mithilfe einer jeweiligen Implementation des BlockData-Interfaces dargestellt.

Siehe auch die Javadocs von org.bukkit.block.data und org.bukkit.block.data.type für eine Liste aller existierenden BlockData-Unterklassen.

Wenn man nun einen Block mit speziellen Eigenschaften hat, und auch weiß welche BlockData-Implementation es für diesen Block gibt, kann man dessen Methoden durch einen einfachen cast benutzen. Beispiel: Piston

// `deinBlock` ist hierbei eine Instanz der Klasse `Block`, die einen Piston in der Welt repräsentiert
Piston pistonData = (Piston) deinBlock.getBlockData();
pistonData.setExtended(true);

Metadatable

Die Klasse Block erbt zudem von Metadatable, was bedeuted dass man Blöcken eigene Metadaten zuweisen kann, um welche sich Bukkit dann kümmert.
Siehe What is Bukkit-Metadata

Inventare

Wenn wir im Code mit Inventaren hantieren möchten, nutzen wir die Klasse Inventory (Wär hätts gedacht).

Inventare erstellen

Die Klasse Bukkit besitzt folgende Methoden, welche zur Erstellung neuen Inventaren genutzt werden:

Erstellung eines Kisten-ähnlichen Inventars mit einer eigenen Anzahl an Reihen. (Der size-Parameter muss ein Multiplikat von 9 sein, und darf 6*9, aka. 6 Reihen, nicht überschreiten)

Erstellung eines speziellen Inventars: (zB. BREWING_STAND)

Inventar öffnen

Die Klasse HumanEntity (Und somit auch die Klasse Player) besitzen folgende Methoden:

Eigenschaften

Events

Siehe org.bukkit.event.inventory für eine Übersicht aller Events im Bezug auf Inventare.
Das wichtigste Event für uns, um auf User-Interaktionen zu interagieren: InventoryClickEvent

Welt

Wenn wir im Code mit Inventaren hantieren möchten, nutzen wir die Klasse World.

Welt kriegen

Um bestehende Welten zu kriegen, bietet die Klasse Bukkit folgende statische Methoden:

Welt erstellen

Um eine neue Welt zu erstellen, bietet die Klasse Bukkit folgende Methode an:

public static World createWorld​(WorldCreator creator)

Im übergebenen WorldCreator-Objekt sind alle Methoden drinnen, die Bukkit zur erstellung der Welt benutzen wird.

Beispiel zum Erstellen einer Welt wäre:

// Konfiguration unserer WorldCreator-Instanz
WorldCreator creator = new WorldCreator();
creator.copy(Bukkit.getWorld("MikasKrasseZombiesMap"));
creator.name("PixelTutorial's Welt")
// Eigentliches erstellen der Welt
Bukkit.createWorld(creator);

Eigenschaften


    • x []()
      • x []()




Spieler

Wenn wir im Code mit Spieler hantieren möchten, nutzen wir die Klasse Player.

Spieler kriegen

Um Spieler zu kriegen, die gerade online sind, bietet die Bukkit-Klasse folgende Methoden:


Eigenschaften

Da die Klasse Player von

erbt, und sogar noch selbst eigene große Liste an Methoden besitzt, werde ich hier keine Auflistung machen!


Metadata

NBT-Tags

Packete

TODO https://wiki.vg/Protocol




Weiteres

Texturenpacket wechseln

Player#setResourcePack​(String url) fordert den Nutzer dazu auf, das angegebene Texturenpacket herunterzuladen und zu benutzen.
Wichtige Sachen, die hierbei zu beachten sind:

  • Clients können Server-Resourcenpackete ablehnen oder komplett abschalten (So dass nichtmal eine Anfrage erscheint)
  • Um das Server-Resourcenpacket vom Client zu entfernen, so dass seine eigenen Resourcenpackete wieder verwendet werden, muss man dem Client ein leeres Resourcen-Packet schicken

Skin ändern

Hier ist ein Code-Snippet welches ich auch Stackoverflow gefunden habe.

public static boolean setSkin(GameProfile profile, UUID uuid) {
    try {
        HttpsURLConnection connection = (HttpsURLConnection) new URL(String.format("https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false", UUIDTypeAdapter.fromUUID(uuid))).openConnection();
        if (connection.getResponseCode() == HttpsURLConnection.HTTP_OK) {
            String reply = new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine();
            String skin = reply.split("\"value\":\"")[1].split("\"")[0];
            String signature = reply.split("\"signature\":\"")[1].split("\"")[0];
            profile.getProperties().put("textures", new Property("textures", skin, signature));
            return true;
        } else {
            System.out.println("Connection could not be opened (Response code " + connection.getResponseCode() + ", " + connection.getResponseMessage() + ")");
            return false;
        }
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    }
}

ActionBar

Die "ActionBar" (oder ChatMessageType.GAME_INFO wie es Spigot nennt) ist ein Text, welcher über der Hotbar des Spielers (für eine gewisse Zeit) angezeigt wird.

Dies ist leider nur durch Packets möglich. Hier eine kleine Referenz-Funktion zum senden einer ActionBar.

public void sendActionBar(Player sendTo, String message){
    message = ChatColor.translateAlternateColorCodes('&', message);
    IChatBaseComponent chatComponent = IChatBaseComponent.ChatSerializer.a("{\"text\": \"" + message + "\"}");
    PacketPlayOutChat bar = new PacketPlayOutChat(chatComponent, ChatMessageType.GAME_INFO);
    ((CraftPlayer)sendTo).getHandle().playerConnection.sendPacket(bar);
}

Title + Subtitle

Ist ein Text, welcher (für eine gewisse Zeit) groß in der Mitte des Bildschirms angezeigt wird.

Früher ging dies nur mit Packets.
Heute gibt es hierzu die Methode sendTitle​(String title, String subtitle, int fadeIn, int stay, int fadeOut).

Anklickbarer Text

"Hologramme"

Feuerwerke

Ping

Um die Latenz eines Spielers zum Server (in ms) zu bekommen, bietet der CraftPlayer schon eine eingebaute Variable.

((CraftPlayer)player).getHandle().ping;

Eigene Crafting Rezepte

Zuerst braucht man eine ItemStack-Instanz des Items, welches beim craften rauskommen soll.
Diese wird dann in den ShapedRecipe Konstruktor als einzigsten Parameter eingespeißt.

  • (Es gibt auch noch eine Methode mit ShapelessRecipe, bei welchem das Format, wie man die Zutaten reinlegt, keine Rolle spielt)
ItemStack bottle = new ItemStack(Material.EXP_BOTTLE);
ShapedRecipe expBottle = new ShapedRecipe(bottle);

Nun müssen wir noch das "Format" setzten, wie es in die Crafting-Table eingesetzt werden muss. Die Funktion nimmt 3 Strings ("Zeilen") an (mit je 3 Zeichen). Jedes Zeichen steht nachher für ein Material.

// Einsetz-Format bestimmen
expBottle.shape("*%*","%B%","*%*");

// Entsprechendes Material für die Zeichen im Pattern setzen
expBottle.setIngredient('*', Material.INK_SACK, 2);
expBottle.setIngredient('%', Material.SUGAR);
expBottle.setIngredient('B', Material.GLASS_BOTTLE);

Damit der Server nun auch dieses Crafting-Rezept hat, muss man es nur-noch hinzufügen.

getServer().addRecipe(expBottle);

Villager Shop

"Eigene" Entities

Bücher erstellen

Amboss-GUI

Scoreboards

Fake-Blöcke

Um den Spieler ein "Block-Veränderung" vorzutäuschen (dem Clienten ein Packet senden, dass sich ein Block verändert hat), gibt es die Methode Player#sendBlockChange​(Location loc, BlockData block) oder auch Player#sendBlockChange​(Location loc, Material material, byte data) (DEPRECATED).
Sobald der Spieler auf diesen Block klickt, wird der eigentliche Block wieder dargestellt!

Fake Schild-Text

Das gleiche gibt es auch speziell für Schilde, welche den Text Clientseitig ändert. Siehe Player#sendSignChange​(Location loc, String[ lines)].