Welcome to the BukkitWiki!

This Wiki is home to Bukkit's documentation and regulations surrounding the Bukkit Project and it's services. Want to help out? We would love to have you! Signup to get started!

Plugin Tutorial/de

From BukkitWiki
(Redirected from HUGE Plugin Tutorial/de)
Jump to: navigation, search
TODOIcon.png
In Arbeit...

Diese Seite ist noch nicht vollständig übersetzt worden.

Contents

Einleitung

Dieses Tutorial wurde von diesem Thread kopiert. Es wurde ursprünglich von Adamki11s geschrieben. Seitdem wurde es mit mehreren Beispielen erweitert.

Adamki11s Bukkit Profil Seite

Nach beenden dieses Tutorials könntest du auf Adamki11s' "Extras" Bibliothek nachsehen welche verschiedene Erweiterte Funktionen bietet.Extras Bibliothek Forum Thread.

Java lernen

Diese Tutorials benötigen Grundkentnisse in der Programmiersprache Java. Wenn du nichts oder nicht viel über Java weist solltest du den Links unten folgen, weil sie dir eine Menge helfen könnten!

Java Video Tutorials

Java Text Tutorials

Wenn du bis jetzt nicht mit der Programmiersprache Java gearbeitet hast solltest du einen Blick auf einige Tutorials werfen.

Java IDEs

Bevor du ein Plugin entwickeln (oder Java lernen) kannst, wirst du eine IDE (Integrated Development Environment) installieren müssen. Dieses ist ein Programm welches du zum kompilieren und debuggen deiner Plugins benötigst. Es existieren derzeit drei populäre IDEs für Java: Eclipse, Netbeans, und IntelliJ IDEA. Eclipse ist das populärste unter Bukkit Entwicklern, während IntelliJ zum größten Teil in der Industrie verwendet wird. Wenn die Entwicklung in Java komplettes Neuland für dich ist, dann ist es empfehlenswert Eclipse zu nutzen, da diese IDE in den meisten Tutorials verwendet wird. Die empfohlene Eclipse Version welche du runterladen solltest ist Eclipse IDE for Java Developers.

Ein exzellentes Eclipse Tutorial kannst du hier finden. Es wurde für Eclipse 3.3 geschrieben, aber das Konzept lässt sich auch auf neuere Versionen anwenden.

Für eine Anleitung um mit IntelliJ zu starten, schaust du dir bitte das folgenden Tutorial an: Getting Started with IntelliJ

Starten eines Plugin Projekts

Ein Projekt erstellen

Vor dem Start wirst du deinen Workspace und Dateien in Eclipse einrichten müssen. Starte Eclipse und erstelle ein neues Projekt in dem du File > New > Java Project: anwählst.

NewJavaProject.png

Nenne das Projekt so wie du willst, dann klick dich durch den new project wizard und folge den Anweisungen auf dem Bildschirm. Ein Ordner wird in der Package Explorer Seitenleiste zu deiner linken erscheinen; Durch einen "links-klick" auf den kleinen Pfeil daneben, wird dir der Inhalt deines Projekts angezeigt.

Einbinden der Bukkit API

Bevor du mit der Entwicklung deines Plugins beginnst, wirst du die Bukkit API als external JAR in dein Projekt einbinden müssen. Du kannst natürlich auch andere APIs welche du nutzen möchtest in dein Projekt einbinden z.B. Permissions.

Die aktuellste Bukkit API kann hier runtergeladen werden: Bukkit API - Development Snapshot


Rechts Klick auf deinen Projekt-Ordner im Package Explorer im Panel zu deiner linken und dann auf Properties, dort wählst du Java Build Path auf der linken Seite aus und der Dialog in deiner Properties Box sollte sich nun ändern. Klicke auf Add External JARs und wähle den Pfad zu deiner runtergeladenen Bukkit API aus.

BuildPathPic.png

Bukkit Javadocs

Wenn du etwas Erfahrung mit Eclipse und Java hast, dann wirst du wissen, dass wenn du mit deinem Mauszeiger über eine built-in Klasse oder Methode fährst, eine gelbe Box aufgeht welche die Dokumentation der Klassen oder Methoden beinhaltet. Dieses ist bekannt als Javadoc und ist auch auf der Oracle Website online zugänglich. Bukkit hat auch eine solche Dokumentation welche oftmals nützliche Beschreibungen der einzelnen Methoden oder Klassen der API beinhalten. Diese ist auch hier online zugänglich. Damit diese Dokumentation auch in Eclipse verfügbar ist, so dass ein Popup erscheint sobald eine Bukkit Methode oder Klasse mit dem Mauszeiger berührt wird, Ist es nötig dass diese Dokumentation erst hinzugefügt wird. Dazu machst du als erstes einen Rechtsklick auf die Bukkit JAR welche unter "Referenced Libraries" in deinem Project Explorer aufgelistet wird und klickst auf "Properties". Wähle "Javadoc Location"  auf der linken Seite des Fensters aus und füge die folgende URL "http://jd.bukkit.org/apidocs/" (ohne Anführungszeichen)  in die Textbox unter "Javadoc URL" ein. Es sollte wie folgt aussehen:

Bukkitjavadocs.png

Klicke auf validate und dann klicke OK. Fertig! Nun sind die Bukkit Javadocs mit dem Bukkit Quellcode verbunden und du kannst die hilfreiche Dokumentation innerhalb von Eclipse aufrufen.

Beginne mit deinem Plugin

Nun musst du ein 'Package' erstellen welches alle Java Klassen beinhalten wird die wir benutzen. Rechtsklicke den Ordner mit dem Namen "src" dann auf New > Package:

MakePackage.png

Benenne dein Package wie folgt:

- Wenn du einen Domain Namen hast, dann würde dein Packagename der Domain Name rückwärts sein.
 - Bsp: i-am-a-bukkit-developer.com dein Package würde dann com.i-am-a-bukkit-developer heißen source
- Du hast keine Domain? Ordne nach bevorzugter Methode.
- Option 1 Erstelle einen Account auf einer Source Control Site wie z.B. GitHub oder SourceForge. - Für GitHub folgst du den Anweisungen hier und wirst dann eine Domain haben, so würde dein Packagename com.github.<username> sein.
- Option 2 Du kannst deine E-Mail Adresse nutzen. Bsp: <username>@gmail.com würde dann com.gmail.<username> sein
- Option 3 Das ist die letzte bevorzugte Methode. Nutze einfach irgendeinen einzigartigen Packagename und nochmals, nutze diese Option als die letzte Möglichkeit.

Da sind einige Dinge mit welchen dein Packagename NICHT beginnen darf:

- org.bukkit
- net.bukkit
- com.bukkit

Wenn du den Basis Packagenamen hast, dann war es das erstmal mit dem Packagenamen. Lass uns die GitHub Domain als Beispiel benutzen. Wenn du ein Plugin erstellst welches "TestPlugin" heißt, dann würde dein voller Packagename wie folgt aussehen: "com.github.<username>.TestPlugin"

Nun da wir unser Projekt fertig aufgesetzt haben, können wir jetzt damit beginnen Klassen hinzuzufügen und unser Plugin zu schreiben. Es ist immer ein gutes Training die Hauptklasse (main class) erst zu erstellen und ihr dann den Namen deines Projekts zu geben. Rechtsklick auf den "src" Ordner wie zuvor und klicke  New > Class. Die Hauptklasse sollte möglichst immer den selben Namen wie das Plugin haben. Zum Beispiel, wenn ich ein Plugin mit dem Namen "TestPlugin" erstelle, dann sollte die Hauptklasse auch "TestPlugin" heißen.

Du hast nun dein Projekt mit der Hauptdatei erstellt. Um Bukkit zu erlauben es zu 'sehen' müssen wir noch folgende plugin.yml Datei hinzufügen. Diese Datei wird essentielle Informationen beinhalten und ohne diese wird dein Plugin NICHT laufen. Dieses mal machen wir einen Rechtsklick auf den Projekt Ordner, NICHT auf den "src" Ordner. Klicke New > File. Nenne die Datei "plugin.yml" und klicke OK. Eclipse wird deine momentan noch leere plugin.yml Datei im default Text Editor öffnen. (Hinweis: Wenn du deinen Workspace organisiert halten willst, dann schließe den Text Editor und ziehe die plugin.yml Datei in den Main Workspace (zu deiner Rechten) und du wirst die Datei innerhalb von Eclipse bearbeiten können.) Da sind zwei essentielle Dinge die du hinzufügen musst; Deine Hauptklassen Referenz und Commands/Usage. Die simpelste plugin.yml Datei würde wie folgt aussehen :

name: <PluginName>
main: <packagename>.<PluginName>
version: <Version Number>

Merke: Der Packagename beinhaltet oftmals den Plugin Namen, also sei nicht überrascht wenn du das hier <pluginname>.<pluginname> am Ende der zweiten Zeile siehst!

onEnable() und onDisable()

Diese Funktionen werden immer dann aufgerufen wenn ein Plugin enabled/disabled wird. Per default wird sich dein Plugin automatisch enablen wenn es geladen wird so dass du deine Events registrieren kannst und Debug Output ausgeben kannst. onEnable() ist das erste was Bukkit aus deinem Plugin laden wird und es wird alles beinhalten, was dein Plugin zum laufen benötigt!

Einführung zu onEnable() und onDisable()

Erstelle die Methoden onEnable() und onDisable() innerhalb der Hauptklasse welche wir zuvor erstellt haben wie folgt:

public void onEnable(){ 
 
}
 
public void onDisable(){ 
 
}

In diesem Moment haben die Methoden noch keinerlei Funktion, allerdings solltest du bemerkt haben, dass es eine Fehlermeldung gibt. Das passiert, weil wir der Hauptklasse erst sagen müssen, dass sie die Funktionalität des Plugins erweitern soll ('extends'). Ändere den Code am Beginn deiner Klasse (direkt unter dem Packagename) von diesem:

Class <classname> {}

Zu diesem:

Class <classname> extends JavaPlugin {}

Nach dem hinzufügen dieses Codes, solltes du eine rote Linie unterhalb von JavaPlugin sehen, diese sagt dir, dass etwas nicht korrekt ist. Um das zu beheben, fährst du mit deiner Maus einfach über den markierten Code und klickst bei dem aufgehenden Popup auf Import 'JavaPlugin' (org.bukkit.plugin.java).

Import JavaPlugin.png

Eine Nachricht mittels Logger ausgeben

Wir werden nun eine simple Nachricht in der Server Console ausgeben, welche mitteilt ob das Plugin enabled oder disabled wird. Als erstes benötigen wir ein logger Objekt welches uns erlauben wird etwas in der Console auszugeben. Um dieses zu tun, müssen wir zuerst den logger in der Hauptklasse definieren:

Logger log = Logger.getLogger("Minecraft");

Dann werden wir innerhalb der onEnable() Funktion eine Nachricht ausgeben, welche sagt, dass das Plugin enabled wurde:

log.info("Your plugin has been enabled.");

Du kannst dann das selbe innerhalb der onDisable() Funktion machen, allerdings solltest du die Nachricht ändern. Deine Hauptklasse sollte nun so aussehen:

package me.<yourname>.<pluginname>;
 
import java.util.logging.Logger;
import org.bukkit.plugin.java.JavaPlugin;
 
public class <classname> extends JavaPlugin {
 
	Logger log = Logger.getLogger("Minecraft");
 
	public void onEnable(){
		log.info("Your plugin has been enabled!");
	}
 
	public void onDisable(){
		log.info("Your plugin has been disabled.");
	}
}

Listener

Siehe dazu: Introduction to the New Event System

Befehle/Commands

Die onCommand() Methode

So, jetzt weißt du wie man Events registriert und wie man etwas macht wenn diese ausgelöst werden, aber was ist wenn du nur etwas machen möchtest, wenn ein Befehl/Command ausgeführt wurde? Richtig, du nutzt die onCommand Methode. Dieser Code wird immer dann ausgeführt, wenn ein Spieler einen Befehl mit vorangestelltem Slash ("/") in den Chat eingibt. z.B. Bei die Eingabe von "/do something" würde die Methode onCommand aufrufen. In diesem Fall würde nichts passieren, da noch kein Code dafür geschrieben wurde.

Vermeide die Benutzung von Befehlen/Commands welche in Bukkit bereits existieren und bedenke wie einzigartig dein Command sein sollte. z.B. das "give" Command wird bereits von unzähligen Plugins verwendet und wenn du nur noch so ein Command wie "give" erstellst, wird dein Plugin sehr schnell inkompatibel zu manch anderen Plugins werden.

Die onCommand Methode muss immer einen boolean Wert zurückgeben - entweder true oder false. Wenn der zurückgegebene Wert true ist dann wirst du kein bemerkenswertes Event sehen. Wenn die Methode false zurück gibt, dann wird der Inhalt deiner Plugin Dateien zurückgegeben 'usage: property' und zeigt dem User eine Nachricht an wie der Befehl zu benutzen ist, wie in der plugin.yml Datei angegeben.

Wenn onCommand genutzt wird, dann solltest du immer vier Parameter registrieren.

  • CommandSender sender - Welcher den Befehl sendet
  • Command cmd - Der Befehl welcher ausgeführt wird
  • String commandLabel - Das Alias welches für den Befehl genutzt wird
  • String[] args - Ein Array von zusätzliches Argumenten, z.B. das schreiben von /hello abc def würde abc in args[0] stecken, und def in args[1]

Einen Befehl erstellen

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if(cmd.getName().equalsIgnoreCase("basic")){ // Wenn der Spieler /basic eingibt, dann tue das folgende...
		doSomething
		return true;
	} // Wenn das passiert, wird die Funktion abbrechen und true als Wert zurückgeben. Wenn nicht, dann wird false als Wert zurückgegeben.
	return false; 
}

Wenn du die onCommand Funktion schreibst, ist es immer eine gute Übung return false am Ende der Funktion zurückzugeben. Die Rückgabe von false zeigt dem User dann die korrekt Benutzung des Befehls an, so wie er in der plugin.yml definiert wurde (siehe unten). Das ist der Weg mit welchem die Hilfe angezeigt wird, wenn etwas falsch eingegeben wird. Sobald ein Wert zurückgegeben wird, die Funktion also true ist, wird die Funktion verlassen und jeglicher Code darunter wird nicht mehr ausgeführt.

.equalsIgnoreCase("basic") bedeutet nur dass zwischen Groß- und Kleinschreibung nicht unterschieden wird. Zum Beispiel, der String "BAsIc" und "BasiC" würden beide gleich dem Command basic sein und der Code würde ausgeführt werden.

Befehl/Command zur Plugin.yml hinzufügen

Es ist außerdem wichtig den Befehl der plugin.yml Datei hinzuzufügen. Füge das folgende am Ende der plugin.yml Datei ein:

commands:
   basic:
      description: This is a demo command.
      usage: /<command> [player]
      permission: <plugin name>.basic
      permisison-message: You don't have <permission>
  • basic - Der Name des Befehls.
  • description - Die Beschreibung des Befehls.
  • usage - Der Hilfe Dialog, welchen die User sehen, wenn die Methode onCommand false zurück gibt. Beschreibe deutlich was der Befehl macht und wie er zu benutzen ist.
  • permission - Das wird genutzt um einigen Hilfe Plugins zu sagen, welche Befehle bei welchen Permissions angezeigt werden dürfen.
  • permission-message - Diese Nachricht wird ausgegeben, wenn die benötigten Rechte zum Ausführen des Befehls nicht vorhanden sind, es aber dennoch versucht wird.

Merke: Die YML-Dateien nutzen zwei bis vier Leerzeichen anstelle von Tabs. Tabs sind nicht gestattet und führen zu Fehlermeldungen.

Konsolen Befehle vs. Player Befehle

Dir ist bestimmt bereits der CommandSender sender Parameter oben aufgefallen. CommandSender ist ein Bukkit Interface welches zwei nützliche (für Plugin Entwickler) Implementationen besitzt: Player und ConsoleCommandSender (um genau zu sein ist Player auch ein Interface).

Wenn du dein Plugin schreibst, dann ist es eine sehr gute Idee sicher zu stellen, dass dein Befehl von der Console ausgeführt werden kann und das Befehle wirklich nur von eingeloggten Spielern ausgeführt werden können. Manche Plugins geben einfach nur zurück ob der Sender kein Spieler ist (z.B. jemand versucht einen Befehl des Plugins von der Console auszuführen), auch wenn solche Befehle einen Sinn ergeben, dass sie von der Console ausgeführt werden (z.B. wechseln des Wetters auf dem Server).

Einen Weg um dieses zu machen ist:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
	Player player = null;
	if (sender instanceof Player) {
		player = (Player) sender;
	}
 
	if (cmd.getName().equalsIgnoreCase("basic")){ // Wenn der Spieler /basic eingegeben hat, dann mache das folgende...
		// tue etwas...
		return true;
	} else if (cmd.getName().equalsIgnoreCase("basic2")) {
		if (player == null) {
			sender.sendMessage("this command can only be run by a player");
		} else {
			// tue irgendwas anderes...
		}
		return true;
	}
	return false;
}

In diesem Beispiel kann der Befehl basic von jedem ausgeführt werden - ein eingeloggter Spieler, oder der Server Operator in der Console. Aber der Befehl basic2 kann nur von eingeloggten Spielern ausgeführt werden.

Hauptsächlich solltest du versuchen so viele Befehle wie möglich für beide ausführbar zu machen, für Spieler sowie auch für die Console. Befehle welche einen eingeloggten Spieler benötigen können den Mechanismus nutzen welcher oben genannt wird um zu überprüfen ob der CommandSender aktuell ein Spieler ist bevor fortgefahren wird. Solche Befehle wären üblicherweise von einigen Eigenschaften des Spielers abhängig, z.B. braucht ein Teleportationsbefehl einen Spieler zum Teleportieren, ein Item gebender Befehl braucht einen Spieler, dem das Item gegeben werden soll...

Wenn du weiter fortschreiten möchtest, könntest du weitere Kontrollen der Befehlargumente einfügen, sodass z.B. ein Teleportationsbefehl, wenn und nur wenn der Name eines Spieler mitgeliefert wird, ausgeführt wird.

Eine separate CommandExecutor Klasse benutzen

Die Beispiele oben haben die onCommand() Methode einfach in die Hauptklasse des Plugins gesteckt. Für kleine Plugins ist das in Ordnung, aber wenn du etwas umfangreicheres schreibst, könnte es Sinn machen, deine onCommand() Methode in eine eigene Klasse zu setzen. Zum Glück ist das nicht allzu schwer:

  • Erstelle eine neue Klasse im Package deines Plugins. Nenne sie in etwa MyPluginCommandExecutor (natürlich solltest du MyPlugin durch den Namen deines Plugins ersetzen). Diese Klasse muss das Bukkit CommandExecutor Interface implementieren.
  • In der onEnable() Methode deines Plugins musst du eine Instanz deiner neuen CommandExecutor Klasse erstellen und sie dann so aufrufen: getCommand("basic").setExecutor(myExecutor);, wobei "basic" der Befehl, den du verarbeiten möchtest, und myExecutor die erstellte Instanz ist.

Am besten wird das anhand eines Beispiels erklärt:

MyPlugin.java (die Hauptklasse des Plugins):

private MyPluginCommandExecutor myExecutor;
@Override
public void onEnable() {
	// ....
 
	myExecutor = new MyPluginCommandExecutor(this);
	getCommand("basic").setExecutor(myExecutor);
 
	// ...
}

MyPluginCommandExecutor.java:

public class MyPluginCommandExecutor implements CommandExecutor {
 
	private MyPlugin plugin;
 
	public MyPluginCommandExecutor(MyPlugin plugin) {
		this.plugin = plugin;
	}
 
	@Override
	public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
		// ... implementation exactly as before ...
	}
}

Notice how we pass an instance of the main plugin to the constructor for MyPluginCommandExecutor. This allows us easy access to the main plugin's methods if we need to.

By doing this, we can better organise our code - if the main onCommand() method is large and complex, it can be split into submethods without cluttering up the plugin's main class.

Note that if your plugin has multiple commands, you will need set the command executor for each command individually.

Ein sicheres "onCommand" schreiben

Wenn Du ein "onCommand" schreibst, ist es wichtig, dass du weder irgendwelche Informationen annimmst, noch dass der Benutzer des Befehls ein Spieler ist. Einige Dinge solltest Du Dir also merken:

Stelle sicher, dass der Sender ein Spieler ist

Schon ein einfacher Code wie dieser, macht das möglich:

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if (!(sender instanceof Player)) {
           sender.sendMessage(ChatColor.RED + "Du musst ein Spieler sein!");
           return true;
        }
        Player player = (Player) sender;
}


Die Länge der Argumente überprüfen

Nimm nicht immer an, dass ein Spieler die richtige Länge von Argumenten verwendet. Um Fehler zu vermeiden, verwende diesen Code:

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	if (args.length > 4) {
           sender.sendMessage(ChatColor.RED + "Zu viele Argumente!");
           return true;
        } 
        if (args.length < 2) {
           sender.sendMessage(ChatColor.RED + "Nicht genug Argumente!");
           return true;
        }
}

Beim herausfinden eines Spielers durch seinen Namen, sei sicher, dass dieser online ist

Manchmal kommt es vor, dass ein Befehl auf einen Spieler aufbaut, der nicht auf deinem Server ist.

public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args){
	Player other = (Bukkit.getServer().getPlayer(args[0]);
        if (other == null) {
           sender.sendMessage(ChatColor.RED + args[0] + " ist gerade nicht online!");
        }
}

Wenn es beabsichtigt sein sollte, dass der Spieler offline ist, kannst du mit der OfflinePlayer-Klasse grundlegende Änderungen vornehmen.

Plugin Konfiguration und Einstellungen

Für weiteres verwende bitte diese Seite: Introduction to the New Configuration/de

Berechtigungen (Permissions)

Mit der neuen Bukkit API für das Berechtigungs-System, könnte es gar nicht einfacher sein. Um herauszufinden, ob ein Spieler eine Berechtigung besitz, verwende diesen Code:

if(player.hasPermission("some.pointless.permission")) {
   // Spieler hat die Berechtigung
}else{
   // Spieler hat die Berechtigung nicht
}

Zudem kannst du auch herausfinden, ob eine Berechtigung gesetzt oder nicht gesetzt wurde (gleich zu Javas null). Dies geht mit einer Funktion:

boolean isPermissionSet(String name)

Vielleicht wunderst du dich, warum es keine Gruppen gibt. Die Antwort ist einfach: Sie werden nicht wirklich benötigt. Früher war die Hauptfunktion von Gruppen, dass die Mitglieder ein anderes Format im Chat besaßen. Das kann man jedoch auch einfach mit dem Berechtigungs-System machen:In deiner Chatplugin-Konfiguration würdest du Verknüpfungen zwischen deinen erstellten Berechtigungen und den Chat-Präfixen herstellen. Als Beispiel: "someChat.prefix.admin" würde für den Präfix [Admin] verwendet werden. Immer, wenn ein Spieler mit dieser Berechtigung etwas in den Chat schreibt, wird dieser Text vor deinem Namen stehen.

Eine weitere vorteilhafte Verwendung von einem Gruppen-System wäre das Senden einer Nachricht an alle Gruppenmitglieder einer Gruppe. Doch auch dies lässt sich mit Berechtigungen machen:

for(Player player: getServer().getOnlinePlayers()) {
 
    if(player.hasPermission("send.me.message")) {
        player.sendMessage("Du bekommst eine Nachricht weil du diese Berechtigung hast.");
    }
 
}

Zum Schluss fragst du dich vielleicht noch, wie man die Berechtigungen von Spielern setzen und einrichten kann, wenn es keine Gruppen gibt. Auch wenn die Bukkit API keine eigenen Gruppen mitliefert, hast du jedoch die Möglichkeit, ein Plugin für dieses Vorhaben zu verwenden. Es gibt viele Plugins für solch ein Vorhaben, zum Beispiel permissionsBukkit.

Configuring your permissions

If you want more control over your permissions, for example default values or children then you should consider adding them to your plugin.yml. This is completely optional, however it is advised. Below is an example permissions config that would be appended to the end of your existing plugin.yml:

permissions:
    doorman.*:
        description: Gives access to all doorman commands
        children:
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false
    doorman.kick:
        description: Erlaubt dir einen User zu kicken
        default: op
    doorman.ban:
        description: Erlaubt dir ein User zu bannen
        default: op
    doorman.knock:
        description: Klopft an der Tür!
        default: true
    doorman.denied:
        description: Prevents this user from entering the door

Firstly, each permission your plugin uses is defined as a child node of the permissions node. Each permission can then optionally have a description, a default value, and children.

Defaults

By default when a permission isn't defined for a player hasPermission will return false. Inside your plugin.yml you can change this by setting the default node to be one of four values:

  • true - The permission will be true by default.
  • false - The permission will by false by default.
  • op - If the player is an op then this will be true.
  • not op - If the player is not an op then this will be true.

Children

Before now you will probably be used to the * permission to automatically assign all sub permissions. This has changed with the bukkit API and you can now define the child permissions. This allows for a lot more flexibility. Below is an example of how you do this:

permissions:
    doorman.*:
        description: Gives access to all doorman commands
        children:
            doorman.kick: true
            doorman.ban: true
            doorman.knock: true
            doorman.denied: false

Here the doorman.* permission has several child permissions assigned to it. The way child permissions work is when doorman.* is set to true, the child permissions are set to their values defined in the plugin.yml. If however doorman.* was set to false then all child permissions would be inverted.

Setting your own permissions

If you wish to know about developing your own permissions plugins (Ones that actaully set permissions) then check out the tutorial on Developing a permissions plugin.

Scheduling Tasks and Background Tasks

Currently, Minecraft servers operate nearly all of the game logic in one thread, so each individual task that happens in the game needs to be kept very short. A complicated piece of code in your plugin has the potential to cause huge delays and lag spikes to the game logic, if not handled properly.

Luckily, Bukkit has support for scheduling code in your plugin. You can submit a Runnable task to occur once in the future, or on a recurring basis, or you can spin off a whole new independent thread that can perform lengthy tasks in parallel with the game logic.

There is a separate Scheduler Programming tutorial which introduces the Scheduler, and gives more information on using it to schedule synchronous tasks, and on kicking off asynchronous tasks in Bukkit.

Block Manipulation

The easiest way to create blocks is to get an existing block and modify it. For example, if you want to change the block that is located five blocks above you, you would first have to get that block and then modify it. As an example  in reaction to a player move event:

public void onPlayerMove(PlayerMoveEvent evt) {
	Location loc = evt.getPlayer().getLocation();
	World w = loc.getWorld();
	loc.setY(loc.getY() + 5);
	Block b = w.getBlockAt(loc);
	b.setTypeId(1);
}

Going over this code we can see that it will change the block that is five blocks above the player to stone whenever the playerMove() event is triggered. Firstly we get the players location, then we get the world from the location. We then modify the co-ordinates of the location by adding five. Now that we have a location and the world we can create a block variable which will hold the block at the location. We get this by using the world and location in w.getBlockAt(loc);. Finally, we have the block at the location so we can change its ID or even it's block data if we wanted to. Block data is specified by a byte so you would have to cast whatever value you set to a byte. For example in addition to the code above you could add b.setData((byte)3);

You can use this to generate buildings and individual blocks programmatically by the use of algorithms. For example to generate a solid cube we could simply make an algorithm with three nested for loops.

public void generateCube(Location point, int length){  // public visible method generateCube() with 2 parameters point and location
	World world = point.getWorld();
 
	int x_start = point.getBlockX();     // Set the startpoints to the coordinates of the given location
	int y_start = point.getBlockY();     // I use getBlockX() instead of getX() because it gives you a int value and so you dont have to cast it with (int)point.getX()
	int z_start = point.getBlockZ();
 
	int x_lenght = x_start + length;    // now i set the lenghts for each dimension... should be clear.
	int y_lenght = y_start + length;
	int z_lenght = z_start + length;
 
	for(int x_operate = x_start; x_operate <= x_lenght; x_operate++){ 
		// Loop 1 for the X-Dimension "for x_operate (which is set to x_start) 
		//do whats inside the loop while x_operate is 
		//<= x_length and after each loop increase 
		//x_operate by 1 (x_operate++ is the same as x_operate=x_operate+1;)
		for(int y_operate = y_start; y_operate <= y_lenght; y_operate++){// Loop 2 for the Y-Dimension
			for(int z_operate = z_start; z_operate <= z_lenght; z_operate++){// Loop 3 for the Z-Dimension
 
				Block blockToChange = world.getBlockAt(x_operate,y_operate,z_operate); // get the block with the current coordinates
				blockToChange.setTypeId(34);    // set the block to Type 34
			}
		}
	}
}

This method will construct a 3D cube or cuboid with the given length and starting point. As for deleting blocks simply follow the same method for creating them but set the ID to 0 (air).

(Player) Inventory Manipulation

Inventory manipulation might seem a little hard at first, but it's really easy once you get the hang of it. This section mostly covers player inventory manipulation, but the same applies to chest inventory manipulation as well if you find out how to get a chest's inventory :P. Here is a simple example of inventory manipulation:

public void onPlayerJoin(PlayerJoinEvent event) {
    Player player = event.getPlayer(); // The player who joined
    PlayerInventory inventory = player.getInventory(); // The player's inventory
    ItemStack diamondstack = new ItemStack(Material.DIAMOND, 64); // A stack of diamonds
 
    if (inventory.contains(diamondstack)) {
        inventory.addItem(diamondstack); // Adds a stack of diamonds to the player's inventory
        player.sendMessage(ChatColor.GOLD + "Welcome! You seem to be reeeally rich, so we gave you some more diamonds!");
    }
}

So inside onPlayerJoin we first make a few variables to make our job easier: player, inventory and diamondstack. Inventory is the player's inventory and diamondstack is a ItemStack that has 64 diamonds. After that we check if the player's inventory contains a stack of diamonds. If the player has a stack of diamonds, we give him/her another stack with inventory.addItem(diamondstack) and send a golden message. So inventory manipulation isn't actually that hard, if we wanted we could remove the stack of diamonds by simply replacing inventory.addItem(diamondstack) with inventory.remove(diamondstack) and change the message a little bit. Hopefully this helped!

HashMaps and How to Use Them

When making a plugin you will get to a point where just using single variables to state an event has happened or a condition has been met will be insufficient, due to more than one player performing that action/event.

This was the problem I had with one of my old plugins, Zones, now improved and re-named to Regions. I was getting most of these errors because I didn't consider how the plugin would behave on an actual server with more than one on at any given time. I was using a single boolean variable to check whether players were in the region or not and obviously this wouldn't work as the values for each individual player need to be separate. So if one player was in a region and one was out the variable would constantly be changing which could/would/did cause numerous errors.

A hashmap is an excellent way of doing this. A hashmap is a way of mapping/assigning a value to a key. You could setup the hashmap so that the key is a player and the value could be anything you want, however the useful things with hashmaps is that one key can only contain one value and there can be no duplicate keys. So say for example I put "adam" as the key and assigned a value of "a" to it. That would work as intended, but then say afterwards I wanted to assign the value of "b" to key "adam" I would be able to and would get no errors but the value of "a" assigned to key "adam" in the hashmap would be overwritten because Hashmaps cannot contain duplicate values.

Defining a HashMap

public Map<Key, DataType> HashMapName = new HashMap<Key, Datatype>(); //Example syntax
 
//Example Declaration
 
public Map<Player, Boolean> pluginEnabled = new HashMap<Player, Boolean>();
public Map<Player, Boolean> isGodMode = new HashMap<Player, Boolean>();

Keep that code in mind because we will be using it for the rest of the tutorial on HashMaps. So, for example lets create a simple function which will toggle whether the plugin has been enabled or not. Firstly, inside your on command function which I explained earlier you will need to create a function to send the player name to the function and adjust the players state accordingly.

So inside on command you'll need this, the function name can be different but for the sake of simplicity it's best if you keep it the same.

Player player = (Player) sender;
togglePluginState(player);

This code above will cast the value of sender to player and pass that arguement to the function togglePluginState(). But now we need to create our togglePluginState() function.

public void togglePluginState(Player player){
 
    if(pluginEnabled.containsKey(player)){
        if(pluginEnabled.get(player)){
            pluginEnabled.put(player, false);
            player.sendMessage("Plugin disabled");
        } else {
            pluginEnabled.put(player, true);
            player.sendMessage("Plugin enabled");
        }
    } else {
        pluginEnabled.put(player, true); //If you want plugin enabled by default change this value to false.
        player.sendMessage("Plugin enabled");
    }
 
}

Now, what this code is doing is checking if the HashMap first contains the key player, so if it has been put into the hashap, if it is then we check the value of the HashMap key by get(player); if this is true then set value to false and send the player a message, else if the value is false then do the opposite, set the value to true and send a message again. But if the HashMap does not contain the key player then we can assume that this is their first run/use so we change the default value and add the player to the hashmap.

More Ideas for HashMaps

A HashMap (or really any kind of Map in Java) is an association. It allows quick and efficient lookup of some sort of value, given a unique key. Anywhere this happens in your code, a Map may be your solution.

Here are a few other ideas which are ideally suited to using Maps. As you will see, it doesn't have to be data that you store per player, but can be any kind of data that needs to be "translated" from one form to another.

Data Value Lookups

public Map<String, Integer> wool_colors = new HashMap<String, Integer>();
 
// Run this on plugin startup (ideally reading from a file instead of copied out row by row):
wool_colors("orange", 1);
wool_colors("magenta", 2);
wool_colors("light blue", 3);
   ...
wool_colors("black", 15);
 
// Run this in response to user commands - turn "green" into 13
int datavalue = 0;
if (wool_colors.containsKey(argument)
    datavalue = wool_colors.get(argument);
else {
    try { datavalue = Integer.parseInt(argument); }
    catch (Exception e) { ; }
}

Saving/Loading a HashMap

Once you know how to work with HashMaps, you probably want to know how to save and load the HashMap data. Saving and loading HashMap data is appropriate if

  • you don't want an administrator to edit the data manually
  • you need to save data in binary format (too complex to organize for YAML)
  • you want to avoid parsing block names and/or other objects from freeform text

This is very simple way how to save any HashMap. You can replace HashMap<Player,Boolean> with any type of HashMap you want. Let's continue using the "pluginEnabled" HashMap defined from the previous tutorial. This code saves the given HashMap to the file with given path.

public void save(HashMap<Player,Boolean> pluginEnabled, String path)
{
	try{
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
		oos.writeObject(pluginEnabled);
		oos.flush();
		oos.close();
		//Handle I/O exceptions
	}catch(Exception e){
		e.printStackTrace();
	}
}

You can see it's really easy. Loading works very very similar but we use ObjectInputStream instead of ObjectOutputStream ,FileInputStream instead of FileOutputStream,readObject() instead of writeObject() and we return the HashMap.

public HashMap<Player,Boolean> load(String path) {
	try{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
		Object result = ois.readObject();
		//you can feel free to cast result to HashMap<Player,Boolean> if you know there's that HashMap in the file
		return (HashMap<Player,Boolean>)result;
	}catch(Exception e){
		e.printStackTrace();
	}
}

You can use this "API" for saving/loading HashMaps, ArrayLists, Blocks, Players... and all Objects you know ;) . Please credit me (Tomsik68) if you use this in your plugin.

/** SLAPI = Saving/Loading API
 * API for Saving and Loading Objects.
 * @author Tomsik68
 */
public class SLAPI
{
	public static void save(Object obj,String path) throws Exception
	{
		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(path));
		oos.writeObject(obj);
		oos.flush();
		oos.close();
	}
	public static Object load(String path) throws Exception
	{
		ObjectInputStream ois = new ObjectInputStream(new FileInputStream(path));
		Object result = ois.readObject();
		ois.close();
		return result;
	}
}

Example implementation of this API: I'm skipping some part of code in this source

public class Example extends JavaPlugin {
	private ArrayList<Object> list = new ArrayList<Object>();
	public void onEnable()
	{
		list = (ArrayList<Object>)SLAPI.load("example.bin");
	}
	public void onDisable()
	{
		SLAPI.save(list,"example.bin");
	}
}

A minor note about this SLAPI and Java's ObjectOutputStream class. This will work un-modified if you are saving almost all well-known Java types like Integer, String, HashMap. This will work un-modified for some Bukkit types as well. If you're writing your own data object classes, and you may want to save their state using this technique, you should read up on Java's Serializable interface. It's easy to add to your code, and it will make your data persistent with very little work on your part. No more parsing!

Maps, and Sets, and Lists, Oh My!

Besides the Map/HashMap classes, Java offers many other data structures. They offer these different classes because there are times when a Map is not the most appropriate. Here's a separate page for discussing Java data structure classes in more detail.

Databases

Sometimes flat files aren't enough for what your looking to do, this is where databases come in. The most common database engines available on Linux/Mac/Windows machines typically run on some flavor of SQL (Structured Query Language).

Software offering SQL allow you to create databases with columns and header to identify to contents of each cell. Think of it as a spreadsheet on steroids, where every column you set up in your database can enforce rules to ensure integrity. Apart from being more organised than a simple custom data file, SQL provides faster access and better searching than flat files.

The SQL standard helps applications like Bukkit implement database storage for their data in a consistent way. Unfortunately, there's more than one SQL-ready database engine, and each has minor differences in how to configure and use it. Which one you choose may depend on your particular needs. (Some plugins even offer configurable options to connect to multiple database engines!)

SQLite

Alta189 has written a fantastic SQLite tutorial which I suggest you watch if you're interested in using SQL in your plugins, included with the tutorials is a handy library you can download and import to make using SQL easier. Once you have watched these video tutorials I would suggest you go and learn some SQL syntax, it's very straightforward and shouldn't take you long to pick up. SQL Tutorials @W3Schools

SQLite is great for very simple databases, because there's no server concerns to set up. Just make a few calls to create a new database and table. It's easy to back up: just copy the whole database file in one go. SQLite is a little bit weaker at data integrity, flexibility in data types, and it may not be something you would want to trust for huge databases of millions of rows. But for a new plugin in development, it's often easiest and fastest to get the SQL basics squared away with SQLite, even if you "graduate" to a more server-class database engine later.

MySQL

Another popular SQL database engine is called MySQL. It is closer to server-grade than SQLite, where many popular companies or websites depend on it for millions of webpage hits every day. With that security comes a little bit steeper learning-curve, because MySQL has more tunable parameters and capabilities.

The coding for plugins accessing MySQL is mostly the same as tiny SQLite or mega-sized Oracle, with only small differences in syntax here or there. But the administration has room to grow. You may want to set up accounts and privileges inside your MySQL setup. You may want to set up SQL scripts that organize your backups and rollback to previous states.

Bereitstellen des Plugins

Nachdem du mit dem schreiben deines Plugins fertig bist, fragst du dich bestimmt wie du dieses zu einer funktionierenden JAR-File machst. First, set up a CraftBukkit server on your local machine. To do this, visit the wiki page on Setting up a server. Next you have to export your plugin to a .jar so that you can run it on your new server. To do this in Eclipse, click File > Export. In the window that pops up, under "Java", select "JAR file", and click next. You will see a window that looks like this:

Eclipse export

On the left, make sure that your src folder is selected. On the right, the two files beginning with a decimal point are to do with Eclipse and are not important to your plugin, so deselect them. It is vital that plugin.yml is selected in order for your plugin to work. Export your JAR file to any destination you like, but make sure you know where it is.

The jar file you have exported should now be a working plugin! Assuming of course that there are no errors in your code or your plugin.yml file. You can now drop the jar file you have exported into your bukkit server's "plugins" folder, reload or relaunch the server, and test away! In order to connect to a server running locally on your computer, simply put "localhost" as the IP address of the server in Minecraft multiplayer. If you run into errors that you can't solve for yourself, try visiting the plugin development forum, asking in the bukkitdev IRC channel, or re-reading this wiki. Once you have a useful working plugin, consider submitting your project to dev.bukkit for consumption by the Bukkit community.

Tips and Tricks

Igniting the Player

The CraftBukkit API is capable of a lot of cool stuff. Here are some code snippets for some nice effects!

How to set someone on fire, in the context of a command:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
    if(cmd.getName().equalsIgnoreCase("ignite")){
        Player s = (Player)sender;
        Player target = s.getWorld().getPlayer(args[0]); // Gets the player who was typed in the command. 
        // For instance, if the command was "/ignite notch", then the player would be just "notch". 
        // Note: The first argument starts with [0], not [1]. So arg[0] will get the player typed. 
        target.setFireTicks(10000);
        return true;
    }
    return false;
}

So if a player ran a command like /ignite Notch then if the player "Notch" was online, Notch would be set on fire!

Killing the player

To keep with the theme, here's a way to kill the player.

Use this for your onCommand method:

public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args){
    if(cmd.getName().equalsIgnoreCase("KillPlayer")){
        Player player = (Player)sender;
        Player target = player.getWorld().getPlayer(args[0]);
        target.setHealth(0); 
    }
    return false;
}

Here is an extension to that, that will kill the player with an explosion:

float explosionPower = 4F; //This is the explosion power - TNT explosions are 4F by default
Player target = sender.getWorld().getPlayer(args[0]);
target.getWorld().createExplosion(target.getLocation(), explosionPower);
target.setHealth(0);

Creating an Explosion

This code produces the TNT/Creeper Visual and Audio effects. This is useful if you are cancelling the TNT explode event but still want the effects.

	public void onExplosionPrime(ExplosionPrimeEvent event){
 
		Entity entity = event.getEntity();
 
		if (entity instanceof TNTPrimed){
			TNTPrimed tnt = (TNTPrimed) entity;
			event.getEntity().getWorld().createExplosion(tnt.getLocation(), 0);
 
		}
	}

Request Section

HowTo: make a player invisible using minecraft packets

HowTo: make plugin using Spout

HowTo: make plugin using maven

Using git, clone the BukkitPluginArchetype repo and build the archetype:

git clone git://github.com/keyz182/BukkitPluginArchetype.git
cd BukkitPluginArchetype
mvn clean install

Now, in the folder you want to create the plugin in, run the following commands:

mvn archetype:generate -DarchetypeCatalog=local

then select the following from the list when prompted:

uk.co.dbyz:bukkitplugin (bukkitplugin)

For groupid, enter what you'd use as the first part of the Java Package.For Artifactid, enter the last part of the package. Accept Version and package as is, then type Y <enter>

E.G.:

Define value for property 'groupId': : uk.co.dbyz.mc
Define value for property 'artifactId': : plugin
Define value for property 'version': 1.0-SNAPSHOT: 
Define value for property 'package': uk.co.dbyz.mc:

You'll now have a folder named as whatever you used for archetypeid. In that folder is a folder, src, and a file, pom.xml.

Open the <archetypeid>CommandExecuter.java file in src/main/java/<package>, and add the following code where it says //Do Something

Player player = (Player) sender;
player.setHealth(1000);

Now, go back to the base folder and run

mvn package

It may take a while downloading things, but let it do it's thing. When done, there'll be a folder called target, and a file called <archetypeid>-1.0-SNAPSHOT.jar inside it. Copy this file to the bukkit server plugin folder and reload the server.

Now, if you type the command /<archetypeid in minecraft while logged into the server, it'll give you full health!


Example Files and Templates


If you have any more questions on this matter, don't hesitate to contact Adamki11s or anyone on the BukkitDev IRC Channel

For further editing or german related questions please contact tr4st here or here.

Language   EnglishБеларускіDeutschEspañolSuomiFrançaisItaliano한국어Nederlands‪Norsk (bokmål)‬PolskiPortuguêsРусский
Personal tools
Namespaces

Variants
Actions
Wiki
Community
Toolbox
Become a Premium Member!