Jump to content

[How-To] Bukkit Plugin Development


Empfohlene Beiträge

Ich melde mich mal wieder mit einem neuen Guide, diesmal zum Thema der Bukkit Plugin Programmierung. Vorerst möchte ich klarstellen, dass ich auch vor nicht allzu langer Zeit mit Java und Bukkit angefangen habe. Ich werde nicht weit gehen können, und nicht alle Fragen klären können, hoffe aber, dass fähigere Developer meinem Beispiel vorran gehen und Fragen klären bzw. die Reihe weiterführen. Wie man aus dem obigen Absatz entnehmen kann, wird das hier eine Art Reihe. Schließlich kann man zum Thema Bukkit Plugin Development keinen Beitrag schreiben und dann sagen "So, gut, das war's jetzt. Es ist alles geklärt". Es kommen immer neue Fragen, Möglichkeiten, Ideen auf. Doch um diese zu lösen, zu nutzen und zu verwirklichen braucht man ein Grundwissen, welches ich gerne mit dieser Reihe vermitteln möchte. Jederzeit kann jedermann einsteigen und weitere Teile zu noch nicht genannten Themen verfassen und Fragen beantworten - das hier wird kein Werk eines Bausteins, das wird das Werk des Forums. Also dann - für alle, die mit dem Programmieren von Bukkit Plugins anfangen wollen: Viel Spaß!

8mWCc.png

Du kannst Bukkit Plugins im Grunde nur mit einem Compile-Programm schreiben - es ist nur furchtbar unschön. Daher widme ich dieses "Kapitel" den Downloads, die ich für komfortables Plugin-Development empfehle. 1. Eclipse Eclipse ist grob gesagt ein Editor. Einen Editor hast bei Windows schon auf deinem PC vorinstalliert, du kannst ihn öffnen, indem du auf der Taskleiste unten Links auf die "Start"-Schaltfläche klickst, und in dem Suchefeld nach "Notepad" suchst. In diesem eher primitivem Editor lässt sich mit Java allerdings nur schwer arbeiten. So gibt dir Notepad nur blank wieder, was du eingibst, während die Methode, die ich vorstellen möchte, alles schön farbig markiert. So wird durch dieses einfache Download aus 8mX7h.png Plötzlich ein viel durchsichtigeres 8mX3F.png Es lohnt sich also wirklich. Außerdem hat dieser Editor noch den Vorteil einer Export-Funktion, die du bei der Programmierung mit Java dauernd brauchen wirst. Sie kompiliert deinen Code, und erspart dir damit die Nutzung eines weiteren Programms, mit dem du deine Plugins immer (de)kompilieren musst. Eclipse kannst du kostenlos auf der Entwickler-Seite runterladen. Hier lädst du dir Eclipse für ein 64-Bit-Betriebssystem, und hier für ein 32-Bit-Betriebssystem herunter. Wie finde ich heraus, ob ich ein 64- oder ein 32-Bit-Betriebssystem habe?

Klicke unten links in der Taskleiste auf die "Start"-Schaltfläche. Klicke auf "Computer" und dann auf "Systemeigenschaften". Dort siehst du in dem Absatz "System" deinen Systemtyp.

Eclipse zu installieren sollte jeder schaffen, wenn nicht, bitte ich denjenigen darum, in Form einer Antwort um Hilfe zu bitten. Beim ersten Öffnen von Eclipse bittet das Programm darum, einen sogenannten "Workspace" auszuwählen. Ich empfehle, einen Ordner auf dem Desktop anzulegen, und diesen als Workspace auszuwählen, denn du wirst den Ordner oft brauchen. In diesem werden sämtliche Projekte, die du mit Eclipse beginnst, gespeichert. Zu dem Thema, wie man nun mit Eclipse umgeht, kommen wir in einem späteren Teil. 2. Das JDK (Java Development Kit) Wir wollen für Bukkit Plugins programmieren. Bukkit arbeitet mit Minecraft, Minecraft mit Java. So lässt sich vereinfacht erklären, warum wir Java benötigen. Wir brauchen allerdings nicht nur "das" Java, welches du schon installiert hast, weil du offensichtlich Minecraft spielst, sondern "das" Java, dass die Entwickler nutzen, die mit Java programmieren.

Keine Sorge; auch dieses Download ist vollkommen kostenlos. Hier findest du eine Liste aller aktuell erhältlichen Versionen des JDK, hier das JDK für ein 32-Bit-Betriebssystem und hier für ein 64-Bit-Betriebssystem. Bitte keinen Installationspfad etc. verändern, einfach alles beim Standard lassen.

Die Installation sollte hier auch kein Problem darstellen. Für alles Weitere müssen wir allerdings feststellen, ob die Installation erfolgreich war. Dazu geh bitte wieder unten rechts in der Taskleiste auf die "Start"-Schaltfläche, dann auf "Computer", in die Festplatte "C:" gehen, und das Verzeichnis "Programme" bzw. "Programme (x86)" bei einem 32-Bit-Betriebssystem öffnen. Darin sollte sich ein Ordner "Java befinden".

Diesen öffnen wir und finden - hoffentlich einen Ordner namens jreX (Java Runtime Environment) und jdkX (X steht für die Version). Sollten die beiden Ordner vorzufinden sein, dürfte die Installation super geklappt haben.

Bei einigen dürfte die Java-Programmierung jetzt losgehen können, bei Anderen müssen noch die Umgebungsvariablen geändert werden. Dafür öffne bitte noch einmal das Startmenü, klicke wieder auf "Computer" und auf "Systemeigenschaften". Links in der Navigation dürftest du "Erweiterte Systemeinstellungen" finden. Dort bitte draufklicken, dann dürfte sich ein Fenster namens "Systemeigenschaften" öffnen. Im Reiter "Erweitert" befindet sich unten der Button "Umgebungsvariablen".

8mYXk.png

Wenn du auf den Button geklickt hast, öffnet sich eine Übersicht der Umgebungsvariablen und Systemvariablen. Scrolle bei den Systemvariablen so lange runter, bis du zu "Path" kommst. Diesen Eintrag bitte markieren und mit einem Klick auf "Bearbeiten" bearbeiten. Den Namen der Variablen solltest du lieber nicht ändern, dafür aber den Wert. In dem zugehörigen Textfeld ganz ans Ende springen (Pro-Tipp: Das geht mit der "Ende"-Taste am Schnellsten).

Sollte die Einträge, die wir jetzt einfügen, schon vorhanden sein, lässt du diesen Schritt bitte aus.

Ansonsten hängst du ans Ende der bestehenden Einträge ein Semikolon ";", auch das bitte auslassen, wenn es schon vorhanden ist. Nun muss der Pfad zum jreX also Java Runtime Environment der Version X rein. Den bekommst du, indem zu wieder in dein Programm-Verzeichnis bis zum Java-Ordner navigierst, den Ordner des jreX öffnest und darin den Ordner "bin" betrittst. Dann klickst du oben in deinem Ordner in die Fläche, wo steht, welche Ordner du nacheinander betreten hast. Ein Pfad sollte markiert werden, diesen kopierst du bitte und fügst ihn in das Feld des Wertes der Variablen ein. Er dürfte in etwa so aussehen:

"C:\Program Files\Java\jre7\bin".

Ans Ende kommt wieder ein Semikolon ";". Nun brauchen wir noch den Pfad zum jdkX, also dem Java Development Kit der Version X. Wenn du dich erinnerst, dieses haben wir vorhin installiert.

Ihn bekommst du auf dieselbe Weise. Im Java-Verzeichnis den jdkX-Ordner öffnen, und den Pfad innerhalb des bin-Ordners herauskopieren. Bei mir sieht er wie folgt aus:

"C:\Program Files\Java\jdk1.7.0_45\bin".

An das Ende dieses Pfades kommt wieder ein Semikolon ";" und wir sind mit den Systemvariablen fertig. Einfach so oft auf "OK" drücken, wie nötig, damit sich alle diesbezüglichen Fenster schließen.

3. Die Bukkit "Library"

Es gilt ein letztes Download zu tätigen. Die Bukkit Library. In ihr befinden sich viele Methoden, die du beim Programmieren deiner Plugins ohne Frage benötigen wirst.

Du findest hier eine Übersicht aller verfügbaren Versionen. Diese Version nutze ich momentan, es ist ein Beta Build für die Minecraft Version 1.7.2. Recommended Builds, die rechts in grün markiert sind, sind absolut spitze. Die meisten Bugs dürften ausgebügelt sein. Die Beta Builds sind schon einigermaßen verlässlich, allerdings nicht so schön wie die Recommended Builds. Und die Development Builds haben aus gutem Grund ihre rote Farbe erhalten. In ihnen kann es durchaus vor Bugs nur so wimmeln. Ich würde versuchen, mich von ihnen fernzuhalten, jedoch ist das Bukkit Team häufig nicht sehr flott im releasen neuer - stabiler - Builds. Und das ist ihnen nicht krummzunehmen, ich wage zu bezweifeln, dass das einer von uns besser könnte als die Jungs von Bukkit.

Eine Version für die gewünschte Minecraft-Version sollte heruntergeladen werden. Der Übersicht zuliebe habe ich sie einmal umbenannt in bukkit_lib.jar. Sie kommt am Besten in den Workspace-Ordner, den wir bei dem 1. Download erstellt oder festgelegt haben.

Wenn all diese Downloads getätigt sind, sind wir erst einmal startklar. Doch zum Development an sich kommen wir im nächsten Teil.

Ich hoffe auf Einiges an Kritik, vielleicht hat ja sogar wer eine Ergänzung oder Frage. Ich bitte darum, nicht direkt Teil 2 mit den ersten Schritten des Programmierens fortzufahren, ich möchte das Ganze in eine gewisse Richtung leiten, damit nicht alles drunter und drüber geht, danach lasse ich das Steuer gerne los.

Abschließend kann ich nur noch viel Glück auf dem Weg des Lernens des Programmierens von Bukkit Plugins wünschen.

Liebe Grüße,

Baustein

Link zu diesem Kommentar
Auf anderen Seiten teilen

Das Projekt gefällt mir wirklich sehr :) Da bekomme ich direkt wieder Lust, das Programmieren von Bukkit-Plugins zu lernen :)

Ich hätte noch eine Anregung, allerdings nicht zu Inhalt: Ich fände es gut, wenn dieses Thema hier moderiert wäre, neue Beiträge also freigeschaltet werden müssen. Das bewart uns davor, dass hier jemand unqualifizierte oder falsche Sachen postet. Falls das dann passiert, kann man am Anfang des Beitrags einen nicht zu übersehenden Hinweis platzieren, dass man diesen Beitrag nicht befolgen soll. Danach kann man dann kommentieren, was daran falsch war und wie man das richtig macht.

Eine solche oder ähnliche Vorgehensweise würde mich freuen, auch wenn es mehr Arbeit ist.

Link zu diesem Kommentar
Auf anderen Seiten teilen

@Baustein: Ich würde mich mal als Helfer bei deinem "Projekt" anbieten. Allein kriegst du das doch nicht hin :D

Aber nun zum eigentlichen Thread: Sehr gute Idee. Das ist für viele sicher auch interessant. Ich würde das JDK vielleicht über Eclipse setzen. Ist in dem Sinne grundlegender ;)

bearbeitet von Arcalio
Link zu diesem Kommentar
Auf anderen Seiten teilen

@Steakbroetchen: Sehr schöne Idee, ich werde gucken, was sich machen lässt.

@Arcalio: Schnüff ^^ So viel traust du mir zu? Naah, hast ja Recht, ich nehme deine Hilfe dankend an :)

Ich habe Eclipse mal über das JDK gestellt, weil das meiner Meinung nach das Schönste zum Einsteigen ist, da man sofort ein Ergebnis sieht, nämlich einen schönen Editor, nicht wie beim JDK, wo man gar nix sieht.

Liebe Grüße,

Baustein

Link zu diesem Kommentar
Auf anderen Seiten teilen

8o2dw.png

In diesem Teil werde ich mit dir dein erstes Plugin programmieren. Es wird ziemlich primitiv sein, aber Kopf hoch! Erfolge kommen in Java schneller, als du denkst. Das Ziel dieses ersten Plugins wird sein, dass beim Starten und Stoppen des Servers eine Nachricht in der Konsole ausgegeben wird. Also fangen wir an. Dafür öffnest du erst einmal Eclipse und wählst den Workspace aus, den du im letzten Part angegeben hast. Nun dürfte es bei dir ungefähr so aussehen: 8nY1M.jpg Das ist die "Willkommens"-Seite von Eclipse. Aber wir brauchen sie nicht. Schließe sie einfach, indem du auf das Kreuz oben über dem Zahnrad wo "Welcome" steht, klickst, oder indem du mit der mittleren Maustaste / dem Scrollrad auf "Welcome" an sich klickst. Auf der linken Seite deines Bildschirms dürfte sich der Project Explorer geöffnet haben. 8nY7h.png Er ist noch komplett leer, aber schon bald wird er dir eine ganze Liste an Projekten bieten. In ihm siehst du alle deine Plugins, kannst sie öffnen, bearbeiten, speichern, exportieren, und und und. Ein jedes Projekt fängt mit dem Project Explorer (nennen wir ihn doch einfach PE) an. So auch dein erstes Plugin. Setzen wir doch den ersten Schritt in die Welt der Bukkit Plugins, mit einem simplen Rechtsklick mitten in den PE. Es sollte sich ein Menü öffnen, in welchem du deine Maus bitte auf "New" bewegst. Beim ersten Öffnen von Eclipse ist die Option, direkt ein neues "Java Project" zu machen, manchmal noch nicht vorhanden. In diesem Fall klickst du einfach auf "Project". 8nYvA.png Hier wählst du das Java Project aus, und klickst auf "Next". In dem Fenster, welches sich nun öffnet, kannst du dem Projekt auch einen Namen geben. In meinem Fall habe ich das Plugin "HelloWorld" genannt, denn wer fängt mit seinen Programmierkünsten nicht mit einem HelloWorld-Programm an? Alle Einstellungen lässt du so, wie sie sind und klickst auf "Finish". Im PE solltest du nun dein neu erstelltes Projekt sehen können. 8nYA3.png Dies ist ein Java-Projekt. Eclipse erstellt beim Hinzufügen eines neuen Java-Projektes immer einen "src"-Ordner, und die JRE System Library. Diese siehst du, wenn du auf den kleinen Pfeil links neben dem Projekt klickst. 8nYCT.png In dem Verzeichnis der System Library fuschen wir lieber nicht herum. Viel mehr interessiert uns der "src"-Ordner. src heißt ausgeschrieben Source. Der folgende Teil mag ein wenig komisch wirken, ist aber bei jedem Erstellen eines Plugins derselbe. Man gewöhnt sich dran. Irgendwann wirst du es auch verstehen, es sorgt auch für Ordnung. Und zwar: Die Packages. Bitte mache einen Rechtsklick auf deinen "src"-Ordner, gehe wieder auf "New" und wähle "Package" aus. Es dürfte sich das folgende Fenster öffnen: 8nYH3.png Als Namen könnten wir hier einen beliebigen Namen nutzen, es gibt allerdings ungeschriebene Regeln, an die man sich halten sollte. Wenn ich dieses Plugin schreiben würde, hieße das Package "de.minecraftforum.helloworld". Man nimmt die Adresse seiner Homepage, also in meinem Fall minecraftforum.de, und packt die Top Level Domain also .de vor die Second Level Domain, also minecraftforum. Sie werden voneinander mit einem Punkt getrennt, sodass de.minecraftforum entsteht. Danach kommt - wieder mit einem Punkt von der SLD getrennt - der Name des Plugins. Üblicherweise ist der Name des Packages komplett klein. So kommt bei mir de.minecraftforum.helloworld zusammen. Bei Herrn Zuckerberg wäre es halt com.facebook.helloworld und so kannst du das auch auf dich anwenden. Wenn du keine Homepage besitzt, denk dir einfach eine andere Methode aus, wie du deine Packages benennst. Niemand wird es sehen, es sei denn, er dekompiliert dein Plugin, oder er bekommt eine Fehlermeldung, das wollen wir jedoch vermeiden ;) Bei größeren Plugins wie zum Beispiel bei Minispielen wirst du verschachtelte Packages erstellen, also Packages in einem übergeordneten Package. Es dient der Ordnung, noch wirst du allerdings nur ein Package benötigen. In diesem Package erstellst du nun eine Klasse. Wieder ein Rechtsklick auf das Package, das sich im src-Verzeichnis befinden sollte, die Maus über "New" halten und "Class" auswählen. Es öffnet sich dieses Fenster: 8nYU3.png Hier müssen wir auch nur einen Namen angeben, den Rest lassen wir beim Alten. Diesen Namen kannst du nun wirklich frei wählen, der erste Buchstabe sollte bloß groß geschrieben sein. Wenn ein Klassenname nicht den Anforderungen von Eclipse entspricht, so sagt das schlaue Programm es dir auch. 8nYYU.png Ich nenne die Klasse einfach "Main", denn sie wird unsere "Main"-Klasse, unsere "Haupt"-Klasse. Mit einem Klick auf "Finish" ist die Erstellung von Dateien erst einmal beendet. Es öffnet sich in der Mitte des Bildschirms die Arbeitsfläche, in der der gesamte Code der Klasse "Main" stehen wird. Eclipse erstellt schon einen kleinen Grundriss. 8nZ1f.png Ganz oben steht der Name des packages, und darunter steht

public class Main {}

Dieser Ausdruck umfasst die ganze Klasse. Nur zwischen den geschweiften Klammern nach Main und unter public wirst du Code erstellen. An dem Ausdruck selbst ändern wir nichts, das ist die Java Syntax wie sie ist und bleiben sollte, wenn man erst in Teil 2 eines Tutorials ist ;). Aber um eine kleine Erklärung zu gewährleisten: "public" steht dafür, dass die Klasse öffentlich zugänglich ist. Von anderen Klassen kann auf sie zugegriffen werden. "class"... Naja, wir haben eine Klasse erstellt. Und "Main" ist der Name der Klasse. Diese Klasse wird Teil eines Plugins für Bukkit. Wenn du dich erinnerst, du hast dir in Teil 1 Schritt 3 die Bukkit Library heruntergeladen. Wie ich schon sagte wirst du diese dauernd benötigen. Dem Projekt haben wir sie allerdings noch nicht zugeordnet. Eclipse kann halt nicht riechen, dass irgendwo in den Tiefen deines Computers eine Library ist, die du gerne nutzen würdest. Dafür machen wir noch einen Rechtsklick auf das Projekt im PE und klicken ganz unten auf "Properties". 8nZiU.png Du wählst bitte "Java Build Path" und den Reiter "Libraries" aus, und klickst auf "Add External JARs...". Es sollte sich ein Fensterchen öffnen, in welchem du bitte zu deiner heruntergeladenen Bukkit Library navigierst und sie auswählst. DIes ist notwendig, damit Eclipse die Bukkit Library auch "kennt" und du damit arbeiten kannst. Sie ist jetzt im Projekt selbst vorhanden. Nun wieder zur Arbeitsfläche. Damit die Klasse die Bukkit Library auch nutzt musst du ihr es befehlen. Das tust du, indem du

extends JavaPlugin

an die Klassendefinition hängst. 8nZom.png Und hier siehst du auch schon eine weiter ganz fabelhafte Funktion von Eclipse. Eclipse unterstreicht alles rot, was es nicht kapiert. Sei es, weil du einen Fehler gemacht hast, oder weil es einfach noch beispielsweise einem sogenannten "Import" bedarf. Ein Import sorgt dafür, dass bestimmte Klassen für diese Klasse genutzt werden. Die Anderen bleiben links liegen und werden nicht unnötig geladen. Ich sagte ja, dass du die Bukkit Library ständig benötigen wirst. Du benötigst also ständig Klassen aus ihr. Und diese musst du auch genauso ständig importieren. Wenn Eclipse dir also etwas rot unterstreicht, so geh einfach kurz mit der Maus über den unterstrichenen Teil, damit Eclipse dir einen Lösungsvorschlag bietet bzw. sagt, was falsch ist. 8nZzS.png Du hast also als Lösungsvorschlag "Import 'JavaPlugin' (org.bukkit.plugin.java)". Das wirst du häufig sehen. Einfach draufklicken, oder - was noch schneller geht - Strg + Shift + O drücken. Das importiert automatisch alles, was du momentan brauchst. 8nZCv.png Keine rot unterstrichenen Stellen mehr. Wunderschön, nicht? Nun noch zu "extends". "extends" bindet eine sogenannte Oberklasse ein. Mit "extends JavaPlugin" legst du also fest, dass deine erstellte Klasse vom Typ "JavaPlugin" ist, es sich also um ein später benutzbares Minecraft-Plugin handelt. Die Oberklasse "JavaPlugin" stellt automatisch diverse Methoden zur Verfügung, welche sichtbar und unsichtbar von Craftbukkit beim Ausführen deines Plugins benötigt werden. Ein wichtiges Beispiel werden wir auch gleich noch kennenlernen. Und weiter geht es mit einer Grundlage, die man wissen sollte. Wenn Bukkit dein Plugin lädt, schaut es immer in der Hauptklasse, in unserem Fall heißt diese "Main" nach einem Teil namens "onEnable". Doch woher soll Bukkit wissen, wie die Hauptklasse heißt, und wo sie zu finden ist? Das müssen wir Bukkit erst mitteilen. Und das geht ganz einfach in Form einer Datei namens plugin.yml. Wir erstellen sie durch einen Rechtsklick auf das Projekt, "New" und "File". 8nZSl.png Als namen geben wir natürlich "plugin.yml" an. Das muss auch so heißen, denn Bukkit sucht bei Plugins immer nach der plugin.yml, und in ihr nach den nötigen Informationen. Wenn du sie erstellt hast, wird sie in deinem als Standard festgelegten Editor geöffnet. Für sie benötigst du Eclipse auch nicht, kannst sie aber natürlich auch innerhalb von Eclipse editieren. 8o00D.png Diese drei Einträge werden immer von Bukkit benötigt um ein Plugin zu laden. Sollten sie nicht vorhanden sein (oder einen Fehler enthalten), so funktioniert gar nichts. Versucht der Server, das Plugin zu laden, findet man in der Konsole einen langen Fehlercode, welcher (zumeist) folgendes Textstück enthält: "Invalid plugin.yml". Dies ist ein sicheres Zeichen für einen Fehler in der plugin.yml. Allerdings kann es auch passieren, dass der Fehler nicht in der Konsole zu finden ist. Eine korrekte plugin.yml ist also entscheidend. Auch die Formatierung ist wichtig. "name", "version" usw. brauchen jeweils eine eigene Zeile, die entsprechenden Werte sind mit einem Doppelpunkt und einer Lücke von ihnen getrennt. Wichtig auch für die spätere Arbeit mit .yml-Dateien: Niemals Tabulatoren nutzen! Alle Abstände entstehen durch Leerzeichen. .yml-Dateien können keine Tabulatoren parsen, sie also nicht "verstehen". Bei "name" kannst du angeben, wie dein Plugin heißen soll. Diesen Namen kannst du auf dem Server beispielsweise dann sehen, wenn du /plugins eingibst. Bei "version" gibst du die aktuelle Version deines Plugins an. Und bei "main" gibst du den Pfad zur Hauptklasse an. Also das Packet, in der sie sich befindet, das ist de.minecraftforum.helloworld und die Klasse selber, mit einem Punkt vom Package getrennt. Später werden in die plugin.yml noch so lustige Sachen wie der Autor / die Autoren, die Kommandos, die Permissions, Plugins, von denen deins abhängig ist, und vieles mehr, hinzugefügt. Für unsere primitiven Zwecke reichen aber die drei Angaben. Nun zurück zu der Klasse "Main". Bukkit weiß jetzt, wo die Hauptklasse ist, hat den Teil "onEnable" aber noch nicht gefunden. Wir müssen ihn noch programmieren. Und damit kommen wir auch schon zu den Methoden. Methoden sind sowas wie in PhP die Funktionen. Sie beinhalten Code, der beim Ausführen der Methode durchgegangen wird. Ein Grundgerüst für eine Methode sieht so aus:

public void methodenName (Parameterart parameterName, Parameterart2 parameterName2) {	Code, der auszuführen ist}

Wobei "public void" häufig anders aussieht, wie zum Beispiel "private boolean", aber dazu kommen wir später. "public" hatten ich schon erklärt und "void" bedeutet, dass die Methode nichts zurückgibt. So auch das Wörterbuch: 8o0w0.png Danach kommt der Methodenname, wobei dieser immer mit einem kleinen Buchstaben anfangen sollte. Besteht der Name aus zwei Teilen, wie z.B. "on" und "enable" so werden sie zusammengeschrieben, aber ab dem zweiten Teil wird der erste Buchstabe immer groß geschrieben. Es heißt also "onEnable" oder "ichBinEineTolleMethode". Diese Art der Groß- und Kleinschreibung nennt sich "Camelcase". Danach kommen immer zwei runde Klammern (). In diesen werden Dinge an die Methode übergeben. Sollten keine Parameter übergeben werden müssen, so bleiben die Klammern leer. Da du für deine erste Methode keine extra Parameter benötigst, sind sie es also. Danach kommen wieder die geschweiften Klammern, und zwischen sie der Code, der bei jedem Aufrufen der Methode ausgeführt wird. Wie schon gesagt sucht Bukkit nach der Methode onEnable() in der Klasse, die wir als Hauptklasse in der plugin.yml verlinkt haben. Alles was darin steht wird beim Laden des Plugins, also beim Hochfahren des Servers oder bei jedem reload einmal ausgeführt. Die Methode sieht demnach wie folgt aus: 8o0Gr.png Und noch einmal: Alles was an der Stelle steht, wo sich momentan der Kommentar "Code" befindet, wird beim Laden des Plugins ein Mal ausgeführt. (Pro-Tipp: Ich habe gerade gesagt, wie man Kommentare macht, die von Bukkit ignoriert werden, und der Übersichtlichkeit dienen. Einfach // schreiben, und alles, was danach in dieser Zeile kommt, ist ein Kommentar! Zu mehrzeiligen Kommentaren später mehr). Zur Wiederholung: Dein erstes Plugin soll beim Hochfahren in der Konsole eine Nachricht ausgeben, und eine Andere beim Herunterfahren. Wie kannst du jetzt Bukkit sagen, er solle Text in der Konsole ausgeben? Das ist einfacher als du denkst. Bevor ich mit Bukkit-eigenen Methoden wie getLogger().info(String message) ankomme, nutze ich lieber vorerst Methoden von Java. Denn Grundkenntnisse im Bereich Java sind für die Programmierung von Bukkit Plugins keinesfalls fehl am Platz. Diese Methode von Java heißt System.out.println(String arg0). Statt "String arg0" geben wir unsere Nachricht an. Sagen wir, die Konsole soll "Hello World" beim Laden des Plugins ausgeben, und das in einem schön knalligen Gelb. Kein Problem. Wir machen einfach

System.out.println("Hello World");

Das Semikolon am Ende ist in Java ganz wichtig. Es signalisiert, dass dieser Befehl fertig ist und hier aufhört. Danach kommt also ein neuer Befehl, der mit dem darüber nichts mehr zu tun hat. Die Methode onEnable() ist damit fertig. Mehr wollen wir ja nicht. Es soll nur diese nette Nachricht ausgegeben werden. Wie ist es aber nun möglich, eine Nachricht in der Konsole auszugeben, wenn der Server, und somit auch das Plugin heruntergefahren wird? Ein kluges Köpfchen wie du sicherlich eines bist, kommt schon auf die folgende Idee: Die Methode, die ausgeführt wurde, wenn das Plugin hochgefahren wurde, hieß "onEnable()". Dann heißt die, die beim Herunterfahren ausgeführt wird sicherlich "onDisable()", oder nicht? Doch, klar! Der neue Code mit der onDisable()-Methode sähe also in etwa so aus: 8o1UF.png Die onDisable()-Methode wird beim Herunterfahren ausgeführt, und in der Konsole wird der Text "See you world" ausgegeben. (Tipp für den Hintergrundwissenwoller: Wird ein Plugin geladen oder deaktiviert, so werden spezielle Funktionen ausgeführt. Doch warum genau diese Funktionen? Das hat mit der bereits erwähnten Oberklasse "JavaPlugin" zu tun, in welcher u.a. genau das festgelegt ist. Hier wird also eine "Funktion" von Oberklassen deutlich) Das Plugin an sich ist hiermit vollendet. Doch leider lässt es sich so noch nicht nutzen. Wir müssen es exportieren. Wie du als Bukkit-Kenner sicherlich weißt, heißen Plugins immer Name.jar. Und Eclipse bekommt jetzt den Auftrag von uns, die Klasse "Main" in dem Package das wir erstellt haben zusammen mit der plugin.yml in einer .jar-Datei zusammenzufassen. Dafür machst du einen letzten Rechtsklick auf das Projekt, gehst auf "Export" und wählst "JAR file" im Verzeichnis "Java" aus. 8o1z2.png Nun nur noch den Namen mit Pfad eingeben und den .classpath und .project den Haken klauen. 8o1CG.png Das Plugin ist jetzt funktionsbereit und kann mit einem Bukkit-Server getestet werden. Das werde ich auch aus Demonstrationszwecken noch erledigen. Beim Starten des Servers gibt die Konsole das Folgende aus: 8o1ZE.png Und nun natürlich noch das Stoppen des Servers: 8o23c.png Und damit ist bewiesen, dass das Plugin fehlerfrei funktioniert. Schon bald werde ich mich an Teil 3 setzen, wo das erste Kommando eingeführt wird. Ich bedanke mich recht herzlich bei Arcalio, der mir auch schon bei diesem Teil mit hochprofessionellen Ergänzungen zu Rate stand, und dem ich einige Textstellen verdanke. Verpasst ihm doch 'n Like ;) Ich wünsche viel Spaß beim Herumexperimentieren mit den bisher vermittelten Kenntnissen. Wie jeder weiß: Probieren geht über studieren ;) Liebe Grüße, Baustein

Link zu diesem Kommentar
Auf anderen Seiten teilen

8pBp8.png

In diesem Teil wirst du ein Plugin schreiben, das ein Kommando zum Server hinzufügt. Es soll den Spieler heilen. Das Ganze machen wir einfach im HelloWorld-Plugin, da brauchen wir dann kein neues Projekt anzulegen. Momentan sieht der Code also so aus: 8pt6U.png Um Kommandos hinzuzufügen gibt es eine nicht ganz einfache Methode, ich werde sie aber Schritt für Schritt erklären. 8ptee.png @Override lassen wir mal links liegen. Der aufmerksame Leser hat mal wieder etwas bemerkt: Das sieht aus wie eine Methode! Und es ist eigentlich auch nichts anderes. public steht dafür, dass die Methode beispielsweise von anderen Klassen aufgerufen werden kann, boolean ist die Sache, die die Methode zurückgibt, nämlich eine Variable des Typs boolean (also true oder false), onCommand ist der Name der Methode, und alles dahinter in Klammern sind die Parameter die übergeben werden müssen, damit die Methode ordnungsgemäß funktioniert. Zuvor hattest du nur mit Methoden zu tun, die nichts, also void zurückgeben. Diese Methode gibt aber true oder false zurück. Du siehst in der vierten Zeile schon "return false". Es wird also "false" zurückgegeben und die Methode wird beendet. Immer wenn im Code "return" steht, so wird die Methode beim Aufrufen dieses Ausdrucks beendet. Alles was danach steht wird nicht mehr aufgerufen. 8ptG8.png Das gibt daher auch einen Error zurück. Wie sollte auch die Zeile mit System.out.println aufgerufen werden, wenn davor schon die Methode beendet wird?? Genauso meckert Eclipse allerdings auch, wenn wir nichts zurückgeben. 8ptMr.png Diese Methode wird automatisch immer dann aufgerufen, wenn ein Kommando eingegeben wird. Wir müssen darin also nur noch abfragen, wie das Kommando hieß! Dafür brauchen wir die sogenannten "if-Abfragen". 8ptTT.png Die Syntax, also Schreibweise einer If-Abfrage, ist die Folgende: 8ptXp.png Wenn nun also die Bedingung, die nach "if" in den Klammern steht, erfüllt ist, wird der Code zwischen den geschweiften Klammern ausgeführt. Bei einzeiligem auszuführenden Code ist die folgende Schreibweise schöner:
if (Bedingung) Code;
Nun muss diese if-Abfrage nur noch auf die onCommand-Methode angewandt werden. Ich zeige einmal den nächsten Schritt, bevor ich ihn erkläre: 8pw08.png Wir machen also eine if-Abfrage. cmd hast du unbewusst schon in den Parametern der Methode festgelegt. Es ist eine Variable "cmd" des Typs "Command". In ihr wird also ein Kommando gespeichert. Bei der Eingabe eines Kommandos in den Chat wird also in der Variable "cmd" das Kommando gespeichert. In der Methode kannst du es für deine Zwecke gebrauchen. getName() ist eine Methode, die die Bukkit Library uns freundlicherweise zur Verfügung stellt. Sie gibt zurück, was der Spieler als Kommando eingegeben hat. Wenn ich /toll in den Chat eingebe, bekomme ich also mit cmd.getName() "toll" zurück. Wenn ich /TOlL in den Chat eingebe, bekomme ich auch "TOlL" zurück. equalsIgnoreCase(String anotherString) gibt mir also mein Kommando zurück, ohne dabei auf Groß- und Kleinschreibung zu achten. /TOlL und /toll sind damit also gleich. Zwischen den Klammern muss ich dieser Methode wieder Parameter geben, nämlich einen String. Ein String ist eine Zeichenkette. "Hallo" ist also ein String. cmd.getName.equalsIgnoreCase("heilen") gibt einen boolean (zur Erinnerung: Das ist true oder false) zurück. Wenn das Kommando, das eingegeben wurde, tatsächlich "unsterblich" ist, so gibt die Methode true zurück. Wenn es "hihihi" ist, gibt die Methode false zurück. Wenn die Bedingung einer if-Abfrage ein boolean ist oder zurückgibt, so wird der Code in der if-Abfrage nur dann ausgeführt, wenn der boolean "true" ist. 8pvKi.png In der ersten If-Abfrage würde der Code also ausgeführt, in der Zweiten nicht. 8pw08.png Der Code wird also nur ausgeführt, wenn das Kommando "unsterblich" ist. Jetzt muss ich dir also bloß noch beibringen, wie du jemanden heilst. 8pwcn.png So würde es schon bis auf ein kleines Problem fabelhaft funktionieren. Bloß muss ich es noch erklären: Player p erstellt eine neue Variable namens "p" des Typs "Player". p ist also ein Spieler. p wird nun ein Wert zugewiesen. Dieser Wert ist der sender des Kommandos, den wir auch oben in der onCommand()-Methode wiedertreffen. (Player) steht dafür, dass der CommandSender zu einem Player wird. Das nennt sich "casten". Darunter werden bloß zwei Bukkit-eigene Methoden aufgerufen. p haben wir erstellt, um schöner auf Bukkit-Methoden für Spieler zugreifen zu können. Wenn du nur p. eingibst, erhältst du eine Liste aller möglichen Methoden, die du nutzen könntest, und die mit einem Spieler agieren. In dieser Liste kannst du natürlich einmal herumstöbern. Unter diesen Methoden kannst du auch setHealth(double arg0) und setFoodLevel(int arg0) finden. Die Parameter sind hier Variablen der Typen double und int. Doubles sind Gleitkommazahlen. 1.45 wäre beispielsweise ein double (Für Java sind Punkte so wie unser Komma, wie man das aus den größten Teilen der Welt halt kennt!). Int steht für Integer. Integer sind ganze Zahlen. Während 1.45 also ein double ist, kann es keine integer sein. 4352 wäre eine Zahl, die man als integer nutzen dürfte. 4352 dürfte aber auch ein double sein, es hat halt nur keine Kommastellen. p.setHealth(20) setzt also das Leben des Spielers p, den wir vorher zum sender des Kommandos ernannt haben auf 20. Das ist sind alle 10 Herzen in Halbschritten abgezählt. p.setFoodLevel(20) ist dasselbe, bloß für den Hunger. Wenn man nun also /heilen eingibt, sollte man geheilt werden. Versuchen wir's. 8pAHr.png Meh, das ging schief. Aber warum? Eigentlich sollte alles so funktionieren, oder nicht? Doch, der Code an sich ist richtig. Doch wie ich schon in Teil 2 versteckt angemerkt habe: Kommandos müssen erst in der plugin.yml registriert werden. Zur Erinnerung: Die plugin.yml sieht momentan noch so aus: 8pANk.png Und nun müssen da noch die Kommandos rein. Dabei muss unbedingt strengstens auf die Einrückung der Elemente geachtet werden! Damit alles vom Server gelesen werden kann, muss es wie folgt aussehen: 8pB3l.png Unter commands zwei Leerzeichen, danach den Namen des Kommandos, ein Doppelpunkt, neue Zeile, 4 Leerzeichen, die usage. Und du kannst mir glauben - so funktioniert das Plugin schon. Es setzt das Leben und das FoodLevel auf das Maximum - genau wie wir wollten. Bloß gibt es zwei kleine Probleme.
  • Jeder kann das Kommando ausführen, auch ohne Permissions etc.
  • Beim Eingeben des Kommandos in der Konsole entsteht dieser Error:
8pBdb.png ihn zu verstehen ist noch viel zu viel verlangt, geschweige denn, ihn zu beheben. Aber dazu komme ich - oder vielleicht ja auch ein neuer, fleißiger Mithelfer - in einem zukünftigen Teil. Probier dich doch mal durch die Methoden, die dir mit p. zur Verfügung stehen! Sollten Fragen bestehen, hab keine Scheu, Eine als Antwort zu stellen. Natürlich würde ich mich über ein "Gefällt mir" am unteren rechten Beitragsrand freuen, sollte dir mein Beitrag tatsächlich geholfen haben. Liebe Grüße, Baustein

8L3sq.png8L3wX.png

Events, Events. Wunderbar. Sie öffnen soooo viele Türen! Aber was ist denn überhaupt ein Event? Storytime_logo3.jpg Eines sommerlichen Sonntagabends dachte sich EvilSeph: "Pour réagir ? des choses qui font de joueurs est beaucoup trop lourd." (= "Auf Dinge zu reagieren, die Spieler machen, ist viel zu schwer.") "Oh! Je suis le développeur principal de Bukkit!" (= "Ach ja! Ich bin ja der Chef-Entwickler von Bukkit!") "Pourquoi dois-je juste bricole pas de moi comme quoi?" (= "Warum bastel ich mir da nicht einfach was?") Er schlug in die Tasten und das wunderbare Ergebnis waren die Events. Ende der Story Time. Es gibt extrem viele verschiedene Events, um nur wenige zu nennen: PlayerJoinEvent, PlayerDeathEvent, BlockBreakEvent, BlockExpEvent, EntityDamageByEntityEvent oder das EntityExplodeEvent. Damit habe ich auch gleich die drei Haupt-Kategorien von Events abgedeckt: Player-Events, Block-Events und Entity-Events. Diese Events werden automatisch zum richtigen Zeitpunkt abgerufen. Wenn ein Spieler auf einen Server kommt, dann mert das das Plugin und führt alle Methoden mit dem "PlayerJoinEvent aus. Wenn er stirbt, passiert dasselbe mit dem "PlayerDeathEvent". Und wenn wir Events abfangen können, können wir richtig viel tolles Zeug damit anstellen. Man könnte... Inventar-Menüs öffnen lassen, Doppelsprünge ermöglichen oder Jetpacks machen. Das nötige Know-How ist noch nicht einmal so groß. Also lass mich direkt anfangen. Im Code müssen wir dem Plugin erst einmal sagen, dass wir in unserer Klasse mit EventHandlern arbeiten wollen. Daher fügen wir hinter das "extends JavaPlugin" noch ein "implements Listener" an. Mach dir über die Bedeutung von "implements" noch keine allzu großen Gedanken, nimm es erst einmal so an. Meine Klasse sieht also momentan so aus:
package de.minecraftforum.meinpacket;import org.bukkit.event.Listener;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin implements Listener {}
Wenn "Listener" rot unterstrichen wird, musst du ihn sicherlich nur noch importieren. Dazu einfach Strg, Shift und O drücken. Das erste Event, das ich dir zeigen werde, wird das PlayerJoinEvent sein. Wie oben schon erwähnt, wird es immer dann aufgerufen, wenn ein Spieler auf den Server kommt. Damit der EventHandler weiß, dass der folgende Teil von ihm zu bearbeiten ist, nutzen wir die "@EventHandler-Annotation". Das ist einfach nur ein @EventHandler vor dem methoden-artigen Konstrukt, das wir gleich erstellen werden. 8KUxc.png "public void" kennst du ja schon. Es bedeutet, dass die Methode "öffentlich" ist, und nichts zurückgibt. Danach habe ich "onPlayerJoin" geschrieben. Das ist aber ein Name, den du beliebig ändern darfst. Normalerweise macht man bei Events allerdings etwas derartiges, aussagekräftiges (und meistens mit dem Präfix "on-"). Nun müssen nur noch in Klammern die Parameter angegeben werden. Hier wird nur ein Parameter benötigt, nämlich der Event-Name. Eine Liste aller Events findest du, wenn du im PE bei deinem Projekt auf "Referenced Libraries" und danach auf die Bukkit-Library doppelklickst, und dir das Package org.bukkit.event.KATEGORIE raussuchst. Ein Beispiel ist org.bukkit.event.Player, wo du mit einer riesigen Menge an Events erschlagen wirst, die nichtmal auf diesen Screenshot passen: 8KULO.png Das bedeutet, PlayerJoinEvent muss auch PlayerJoinEvent bleiben, damit es erkannt wird. Gäbe auch Errors, wenn diese Regel nicht befolgt wird, und du etwas Beliebiges dort reinschreibst. Statt "event" darfst du allerdings wieder machen was du willst, es wird nur später wichtig, um darauf zurückzugreifen. Okay, was soll das Ziel des Plugins sein? Ich würde sagen, ein gutes Ziel wäre es, wenn bei jedem Auf-Den-Server-Kommen eine selbst ausgedachte Nachricht auf dem Server das langweilige "Bla has joined the game" ersetzt und der entsprechende Spieler noch eine persönliche Willkommens-Nachricht erhält. Das erste Ziel ist schnell erreicht. Wenn du einfach nur "event." eingibst, bekommst du eine Auswahl an möglichen Methoden vorgelegt. Eine davon ist "setJoinMessage(String joinMessage)" Also - ganz klarer Fall - machst du event.setJoinMessage("Deine Join-Nachricht"); Ich habe daraus mal das hier gemacht: 8KV56.png Diese Join-Nachricht ist vielleicht schon ganz nett, aber sie nennt den anderen Spielern nicht einmal den Namen des Neuen. Den Namen herauszufinden ist ganz leicht. Wie der Name des Events schon sagt, kann es nur von einem "Player", also Spieler ausgerufen werden. Vielleicht hast du vorhin schon gesehen, dass dir mit "event." die Methode getPlayer() angezeigt wird. Die nutzen wir jetzt. Den Spieler speichern wir in einer Variable des Typs Player. Macht Sinn, nicht? 8KWjV.png Jetzt ist also in "player" des Typs "Player" der Player gespeichert. Auch in Zukunft wird dir auffallen, dass Java sehr repetiv ist und sich viele Ausdrücke innerhalb einer Zeile wiederholen. Warum "player" gelb unterstrichen ist? Gelb angemakerte "Fehler" sind erstmal nicht schlimm, sie zeigen dir nur an, dass du diese Variable noch nie benötigt hast, und die Initiierung somit sinnlos ist (Kleiner Hinweis am Rande: Gelb werden auch noch veraltete Methoden, Warnungen und Andere markiert. Es ergeben sich allerdings keine Compiler-Fehler, es liegt einfach nur eine schlechte / unnötige Programmierung vor). Aber da die Variable gleich noch gebraucht wird, mach dir nichts draus. In diesem Absatz soll es nun um die Verknüpfung von Strings, also Zeichenketten gehen, denn das brauchst du gleich und auch in deiner kommenden Programmierer-"Karriere". Ein Beispiel für einen verknüpften String wie ich ihn gerne nenne wäre: 8L179.png "Da kam " ist ein String, player.getName() gibt einen String, nämlich den Namen des Spielers "player" zurück, und " auf den Server" ist auch ein String. Die Verteilung der Leerzeichen mag erst komisch erscheinen, doch auf den zweiten Blick macht es Sinn, da sonst der Spielername direkt zwischen "kam" und "auf" erscheinen würde, in meinem Fall wäre die Nachricht also "Da kamBausteinauf den Server!" Und damit ist auch schon geklärt, wie du den Namen herausfindest. Da ich natürlich nicht alle Methoden hier vorstellen kann möchte ich dir nur nochmals ans Herz legen, selber nach Methoden zu suchen. Beispielsweise alle für "player" verfügbaren Methoden siehst du ja, wenn du "player." eingibst. Was war noch das nächste Ziel? Achja! Der Spieler soll eine persönliche Nachricht erhalten. Sagen wir doch einfach "Grüß dich, SPIELERNAME". Ganz gerne würde ich dich mit dieser Aufgabe alleine lassen, doch das kann ich in einem How-To wohl nicht bieten. Stattdessen pack ich die Lösung mit einer kleinen Erklärung in einen Spoiler und empfehle dir, es vor dem Spicken selbst auszuprobieren.

8L1VG.png Die Methode sendMessage(String arg0) benötigt einen String als Parameter, in diesem Fall setzt er sich aus "Grüß dich, " und dem Spielernamen zusammen.

Der ein oder andere wird das Plugin jetzt schon exportiert haben und hat dann mit Sicherheit entnervt festgestellt, dass nichts, aber auch GAR NICHTS funktioniert. Habe ich euch Mist erzählt? Nein, nur ein Punkt fehlt noch: EventListener müssen registriert werden. Merke: In jedem Plugin, welches EventHandler nutzt, wird eine Registrierung des Listeners im onEnable()-Teil benötigt. Diese sieht wie folgt aus, wenn der EventListener in der Hauptklasse ist: 8L2ep.png Über die registrierung von externen EventHandlern sprechen wir in einem späteren How-To. Jetzt geht's nur noch ans Testen. Ich exportiere das Plugin, lade es auf den Server, connecte, und tatsächlich: 8L32O.png Und für alle anderen: 8L36m.png es hat also alles ganz perfekt funktioniert smile.png Ich hoffe, dass ich die Neugier in deinem Köpfchen anregen konnte. Denn mit Events kannst du so extrem viel machen! Versuch's einfach mal wink.png Liebe Grüße, Baustein

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 2 months later...

So_29_Jun_14___13:24:16.png

bc6296252755d22ea27cfa09cf8c56dd1.png (Ja, ich hab die Überschrift nicht mehr richtig hinbekommen, PC neu aufgesetzt und jetzt auf LM....) Dieses Thema wird Spaß machen. Ehrenwort. Die Überschrift heißt ja schon "Passenger". Aber was kann man sich darunter vorstellen? Ein gutes Beispiel sind die Spinnen-Reiter, die es in Vanilla Minecraft gibt. 513px-Spider_Jockey.png?version=de63ef66 Hier ist das Skelett der Passenger von der Spinne. "Passenger" heißt nichts anderes als Passagier. da0d4eafe70d62be570c3bca288b5e63.png Und mit diesen Passagieren lassen sich interessante Dinge verhackstückeln. Ich weiß nicht mehr genau, auf welchem Server das war, aber auf einem der großen Minispiel-Giganten konnte man in der Lobby auf Leute rechtsklicken und saß dann auf deren Kopf. Das klappt mit Passangern. Und das werden wir heute auf ähnliche Weise machen. Also, hauen wir rein: Die Klasse "MeineKlasse" im Packet "de.minecraftforum.meinpacket" habe ich mal wieder leer gemacht:

package de.minecraftforum.meinpacket;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin { }

Das Ziel des Plugins soll sein, dass wenn man einen Rechtsklick auf einen Mob oder einen Spieler macht, man zu seinem Passenger wird. Um auf einen Rechtsklick hören zu können, wird natürlich wieder der Listener gebraucht.

public class MeineKlasse extends JavaPlugin implements Listener {

(Nicht vergessen, Listener importieren! Dafür mit der Maus drüberhalten und die entsprechende Option auswählen oder einfach Strg + Shift + O drücken!) Jetzt muss noch herausgefunden werden, wie das Event heißt, welches bei einem Rechtsklick ausgeführt wird. Und da wird's gar nicht so leicht. Es gibt nämlich zwei ganz ähnliche solche Events. Das PlayerInteractEvent (ich nenne es auch gerne Kuchen) und das PlayerInteractEntityEvent. Der Unterschied zwischen den beiden ist, dass das PlayerInteractEvent bei jedem Klick aufgerufen wird. Also bei einem Linksklick auf einen Block, in die Luft, bei einem Rechtsklick auf einen Block und in die Luft. Das PlayerInteractEntityEvent wird allerdings nur aufgerufen, wenn der Spieler einen Rechtsklick auf eine Entity macht. Und genau das wollen wir wissen, denn jeder Mob und jeder Spieler ist eine Entity. Ja, auch andere Dinge wie beispielsweise ein fliegender Pfeil sind Entities, dazu kommen wir gleich. Der Nachteil vom PlayerInteractEvent wäre hier, dass wir nur schlecht rausfinden können, ob die Interaction auf eine Entity stattgefunden hat, während diese Funktion im PlayerInteractEntityEvent schon längst enthalten ist. Also, folgende Methode:

@EventHandler public void onRightclickEntity(PlayerInteractEntityEvent e) { }

Ich denke, ich brauche da nicht viel zu erklären, wer das nicht versteht soll bitte nochmal im EventListener-Part vorbeischauen. (Tipp: Das ist der vorige ;)) Wie ich schon sagte, Entities sind nicht nur Mobs und Spieler. Um herauszufinden, was für Bukkit eine Entity ist, und was nicht, kannst du auf die Enumeration "EntityType", die dir von Bukkit gestellt wird, zugreifen. Eine Enumeration ist sozusagen eine Liste. Später wirst du beispielsweise ganz oft mit der "ChatColor"-Enumeration arbeiten. Enumerationen sehen immer wie folgt aus:

Name.EINTRAG

Wie gesagt, der Name der Enumeration, die die Entity-Typen beinhaltet ist "EntityType". Wie abwegig... ^^ Also, klarer Fall:

EntityType.EINTRAG

Was jetzt ein Eintrag ist weißt du nicht, das willst du ja herausfinden. Und wieder musst du nur das machen, was ich dir schon in den vorigen Parts gepredigt habe: EntityType. eingeben und sehen, was dir die Welt so bietet:

  • Arrow
  • Bat
  • Blaze
  • Boat
  • Cave-Spider
  • Chicken
  • uvm.

das sind alles Entities. Und das Ziel des Plugins soll es nicht sein, auf solchen Entities reiten zu können, sondern Passenger von Mobs sein zu können. Es wäre auch sicherlich (ich habe das noch nicht ausprobiert - das müsste ich mal machen) problematisch, Passenger von einem Item zu sein, denn auch das ist eine Entity. Also muss Unterschieden werden, ob die Entity ein Mob bzw. Spieler ist oder nicht. Und das geht viel einfacher als du denkst. In Bukkit wird nämlich freundlicherweise unterschieden zwischen Entity und LivingEntity. 98056c9e8108e98b1eaad2aa437c9239.png Es muss also geprüft werden, ob die Entity, auf die der Player geklickt hat, eine LivingEntity ist, oder nicht. Ich zeig dir erstmal den Code, der dafür vorgesehen ist, und dann gehen wir ihn gemeinsam durch.

@EventHandlerpublic void onRightclickEntity(PlayerInteractEntityEvent e) { Entity entity = e.getRightClicked(); if (entity instanceof LivingEntity) { LivingEntity livingEntity = (LivingEntity) entity; }}

Also erst einmal speichere ich hier die Entity, die in dem Event angeklickt wurde. Ja, die Methode getRightClicked() ist ein wenig abwegig, ich habe - das muss ich ehrlich zugeben - auch erstmal gestutzt, als es die Methode getEntity() nicht gab. Aber bei genauerem Betrachten macht das schon Sinn, schließlich kann die Methode getRightClicked() nur eine Entity zurückgeben, da diese Methode ja nur bei einem Rechtsklick auf eine Entity ausgerufen wurde. Danach ist der Check, ob die Entity eine LivingEntity ist. Und das ist auch gar nicht mal so schwer zu verstehen. So ein Check wird eigentlich auch immer bei Kommandos gemacht, wenn diese nicht von der Konsole ausgeführt werden sollen. 5707ce69ab605a44d40c6d80b0552bbb.png Der Part in dieser if-Abfrage wird also nur ausgeführt, wenn die Entity eine LivingEntity ist. Und dann können wir auch diese festlegen. Eclipse moppert rum, wenn wir nur

LivingEntity livingEntity = entity;

machen. Ist ja auch klar, schließlich muss entity keine LivingEntity sein und das könnte zu großen Fehlern führen. Ich finde solchen Code auch ziemlich hässlich, das ist wie wenn man sagt: LKW = Motor Das ist unsauber. Schon bei den Kommandos habe ich dir beigebracht, zu "casten":

Player p = (Player) sender;

hieß es damals. Die Variable "sender" wird zu dem Variablentyp "Player" gecastet. Bei dem Entity-Beispiel wird die Variable "entity" zu dem Variablentyp "LivingEntity" gecastet. Eigentlich genau dasselbe ;) Der oben genannte Fehler, dass eine Entity nicht gleich eine LivingEntity sein muss, kann nicht auftreten, weil das ja durch die vorige if-Abfrage abgefangen werden würde. Was muss noch getan werden?

  • Der Spieler, der geklickt hat, muss Passenger werden

Na, die Liste ist doch nicht allzu lang. Also nichts wie ran an den Speck: Ich schick dir wieder den Code voraus:

@EventHandlerpublic void onRightclickEntity(PlayerInteractEntityEvent e) { Entity entity = e.getRightClicked(); if (entity instanceof LivingEntity) { LivingEntity livingEntity = (LivingEntity) entity; Player p = e.getPlayer(); livingEntity.setPassenger(p); }}

Also, was hab ich angefügt... Ich habe den Spieler aus dem Event genommen, das hatten wir ja auch schon im letzten Teil, das muss ich nicht näher erläutern. Ja, und dann habe ich nur noch mit der wunderbaren Methode setPassenger(Entity e) den Passenger gesetzt.... Achja, in Java läuft halt alles schön intuitiv ab. Jetzt muss das Event natürlich noch registriert werden. Wer das Plugin bis jetzt getestet hat dürfte auf enttäsuchende Ergebnisse gestoßen sein. Dafür nehmen wir denselben Code aus dem letzten Teil, ich habe es hier noch einmal neu geschrieben:

public void onEnable() { getServer().getPluginManager().registerEvents(this, this);}

Die gesamte Klasse sieht jetzt also so aus:

package de.minecraftforum.meinpacket;import org.bukkit.entity.Entity;import org.bukkit.entity.LivingEntity;import org.bukkit.entity.Player;import org.bukkit.event.EventHandler;import org.bukkit.event.Listener;import org.bukkit.event.player.PlayerInteractEntityEvent;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin implements Listener { public void onEnable() { getServer().getPluginManager().registerEvents(this, this); } @EventHandler public void onRightclickEntity(PlayerInteractEntityEvent e) { Entity entity = e.getRightClicked(); if (entity instanceof LivingEntity) { LivingEntity livingEntity = (LivingEntity) entity; Player p = e.getPlayer(); livingEntity.setPassenger(p); } }}

Jetzt noch der Beweis, dass es klappt: a4f0cadf0438457ef7217dce7fd0b7e0.png Wie ich auch den Golem gekommen bin? Da müsst ihr mir einfach glauben, dass das durch den Rechtsklick funktionierte. Wenn ihr's mir nicht glaubt - einfach ausprobieren! wink.png Wer Fragen hat, soll diese natürlich gerne hier auch drunter schreiben! Ich beiße nicht. Liebe Grüße, Baustein 6853d5fd96b090dcbaa00605bb385525.png SQL. SQL ist ein fantastischer Weg, Daten zu speichern. Du kannst dir heutzutage sogar kostenlos im Internet MySQL-Datenbanken machen lassen. In diese kannst du dann von deinem Plugin aus Daten hochladen, um sie von anderen Servern oder beispielsweise nach einem Neustart noch erreichen zu können. Ich gehe mal davon aus, dass du schon eine SQL-Datenbank hast und auch die Zugriffsdaten kennst. Es wäre auch von Vorteil, wenn dir ein paar Befehle geläufig sind, wenn nicht, dann musst du halt versuchen, sie im Folgenden zu erlernen. Damit du in deinen Plugins mit SQL arbeiten kannst, musst du eine Verbindung zu der Datenbank haben. Diese muss also geöffnet und geschlossen werden. Dann musst du noch vom Plugin aus in der Datenbank etwas ändern können, und optional noch ein Ergebnis daraus bekommen. Da die Methoden dafür nicht ganz einfach zu erstellen sind, hat mein herzallerliebster Arcalio eine SQL-Klasse geschrieben:

import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultSet;import java.sql.SQLException;import java.sql.Statement;public class SQL {public static Connection con;public static void connect(String host, String db, String user, String pass, String port) { try { con = DriverManager.getConnection("jdbc:mysql://" + host + ":" + port + "/" + db,user,pass); System.out.println("Verbindung zu MySQL-DB hergestellt."); } catch (SQLException e) { e.printStackTrace(); }}public static void close(){ if (con!=null){ try{ con.close(); System.out.println("Verbindung zu MySQL-DB geschlossen."); }catch (SQLException e){ e.printStackTrace(); } }}public static void update(String qry){ try{ Statement stmt = con.createStatement(); stmt.executeUpdate(qry); }catch (SQLException e){ e.printStackTrace(); }}public static ResultSet query(String qry){ ResultSet rs = null; try{ Statement stmt = con.createStatement(); rs = stmt.executeQuery(qry); }catch (SQLException e){ e.printStackTrace(); } return rs;}}

In dieser Klasse sind alle solchen Methoden schon vorgefertigt. Um sie in deinem Projekt nutzen zu können, machst du jetzt das Folgende:

  • Rechtsklick auf dein Packet
  • New
  • ---> Class
  • Als Name "SQL" angeben
  • Zurück ins Forum gehen
  • Den oben stehenden Code kopieren
  • In die Klasse einfügen

Beim Einfügen musst du darauf achten, dass du

package de.minecraftforum.meinpacket;

drin lässt, ansonsten klappt nämlich gar nichts squint.png Unsere Haupt-Klasse habe ich wieder geleert:

package de.minecraftforum.meinpacket;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin { }

Als Ziel des Plugins schlage ich vor, dass wir in einer Datentabelle auf der Datenbank speichern, wie oft ein User auf unseren Server gekommen ist. Jetzt hast du hoffentlich schon geahnt, dass dafür das PlayerJoinEvent benötigt wird, und deshalb muss wieder das "implements Listener" an die Klasse.

public class MeineKlasse extends JavaPlugin implements Listener {

Na dann. Was braucht man für Events? Genau, die Registrierung. Also einen onEnable()-Teil schreiben:

public void onEnable() { getServer().getPluginManager().registerEvents(this, this); }

Und der onEnable()-Teil wird auch für SQL eine wichtige Rolle spielen. Denn wie du weißt wird diese Methode immer ausgeführt, wenn das Plugin geladen wird. Und es macht doch Sinn, in dem Moment die Verbindung zu der Datenbank herzustellen! Dafür triffst du die folgenden Änderungen:

public void onEnable() { SQL.connect(host, db, user, pass, port); getServer().getPluginManager().registerEvents(this, this); }

Jetzt werden bei dir Sicherlich die Parameter von connect() rot unterstrichen sein. Das sind alles Strings und stellen die benötigten Zugangsdaten für die Datenbank dar - die kann Java ja nicht riechen, wir müssen sie ihm also geben. Später könnte man sowas mit einer config.yml machen, aber dazu kommen wir in einem späteren Teil. Lass uns hier einfach die Zugangsdaten innerhalb des Plugins festlegen. Wichtig: Wenn du sowas machst, dann solltest du dein Plugin an niemanden weitergeben. Du magst vielleicht denken, dass derjenige deine Zugangsdaten eh nicht sehen kann, du schickst ihm ja das exportierte Plugin, aber solche .jar-Dateien lassen sich dekompilieren und schuppdiwupp - er hat deine Daten! Also - wenn du nicht mit einer Config arbeitest, behalte das Plugin lieber für dich! Dann müssen wir also noch die Strings festlegen:

public class MeineKlasse extends JavaPlugin implements Listener { String host, db, user, pass, port; public void onEnable() { host = "localhost"; db = "database"; user = "me"; pass = "password"; port = "3306"; SQL.connect(host, db, user, pass, port); getServer().getPluginManager().registerEvents(this, this); }}

Zuerst habe ich alle Strings erstellt, und danach in der onEnable definiert. So könnten die Zugangsdaten aussehen, wenn die MySQL-Datenbank auf demselben Server wie der Minecraft-Server läuft, die vergebene Datenbank den kreativen Namen "database" hat, der User "me" ist und das Passwort "password" heißt. Den Port kannst du im Regelfalle so lassen, der ist eigentlich immer 3306. Wenn die Zugangsdaten, die du angibst, für deine Datenbank tatsächlich richtig sind, dann wird jetzt schon beim Plugin-Start eine Verbindung zu der Datenbank aufgebaut. Sollten die Zugangsdaten falsch sein, so darfst du dich nicht wundern, wenn ein großer Error beim Serverstart geworfen wird. Auf zum nächsten Schritt! Beim Server-Stopp oder beim Disablen des Plugins soll die Verbindung wieder geschlossen werden. In einem der früheren Teile habe ich das schon einmal angesprochen. Es gibt die onEnable()-Methode, mit der du schon viele Events registriert hast, aber auch die onDisable()-Methode. Und die müssen wir nutzen.

public void onDisable() { SQL.close(); }

Auch hier lässt sich auf eine wunderbare Methode von Arcalio zugreifen, die in der SQL-Klasse enthalten ist. So wird automatisch bei jedem "Ausschalten" des Plugins die Verbindung ordnungsgemäß geschlossen. (Auch hier wird bei einer SQLException ein Errorlog ausgegeben!) Nun steht also die Verbindung zu der MySQL-Datenbank. Dann muss ja nur noch in der Datenbank etwas geändert werden, oder nicht? Ja, das stimmt schon, aber das geht leider nicht gaaaaanz so einfach. Na los, hauen wir in die Tasten. Zuerst natürlich das Grundgerüst für das Event:

@EventHandler public void onPlayerJoin(PlayerJoinEvent e) { }

So. Und jetzt fängt's auch schon an, es müssen einige Überlegungen getroffen werden:

  • Wenn der User auf den Server kommt, so wird in der Datenbank ein Eintrag für ihn gemacht

Das ist überhaupt nicht Backend-freundlich, da dann bei mehreren Joins ganz viele Einträge für diesen Spieler wären. Stattdessen wäre es viel schöner, wenn es für jeden Spieler eine Zeile in der Datentabelle gibt, in der wiederum in einer Spalte "joins" ein Counter nach oben zählt.

  • Wenn ein User auf den Server kommt, so wird sein Join-Eintrag in der Datenbank um 1 erhöht.

Schon besser. Aber was, wenn ein User das erste mal auf den Server kommt und noch keinen Eintrag hat? Dann kann auch nichts erhöht werden.

  • Wenn ein User auf den Server kommt, so wird geschaut, ob es einen Eintrag für ihn gibt. Wenn ja, dann wird sein Join-Eintrag in der Datenbank um 1 erhöht, und wenn nein, dann bekommt er einen Eintrag mit dem Join-Wert 1.

Genau so muss es sein. Klingt vermutlich ein wenig schwieriger, als du es dir am Anfang vorgestellt hast. Aber wir schaffen das ;) In Arcalios SQL-Klasse gibt es dafür 2 unterschiedliche Methoden: Die update(String qry)-Methode und die query(String qry)-Methode. Sie unterscheiden sich dadurch, dass die update()-Methode nur etwas in der Datenbank ändert, und die query()-Methode ein sogenanntes ResultSet zurückgibt. d8e2a134992a13714d6f430718b13f15.png5b17802b0410b77d8b412b4d33035343.png Wir können also mit query() Abfragen machen und mit update() Änderungen treffen. Lass uns zunächst schauen, ob der User einen Eintrag hat.

ResultSet rs = SQL.query("SELECT * FROM minecraftforum WHERE player='" + e.getPlayer().getName() + "';");

Das wäre eine typische solche Abfrage. Mit SELECT * werden alle Eintrage aus (FROM) der Datenbank, die wir in db gespeichert haben aufgerufen, mit der einzigen Bedingung, dass in der Spalte "player" der Name des Spielers des Events steht. Ja, ich bin mir bewusst, dass wir noch gar keine solche Datentabelle angelegt haben. Doch das obige Beispiel dient mir aus einem anderen Zweck. Ich möchte dir damit zeigen, dass du es lieber sein lassen solltest, in Datenbanken, die nicht beispielsweise nach einem Server-Neustart zurückgesetzt werden, mit Spielernamen zu arbeiten. Denn die können sich ja ab der 1.8 ändern. Stattdessen solltest du die UUIDs nutzen. Das sind "Universally Unique Identifier" und jeder Spieler hat nur eine. Daran kannst du sie also unterscheiden, selbst wenn sie ihren Namen ändern. An die UUID eines Spielers kommst du wie folgt:

@EventHandler public void onPlayerJoin(PlayerJoinEvent e) { Player p = e.getPlayer(); UUID uuid = p.getUniqueId(); }

Oder natürlich - wer's schnell mag:

@EventHandler public void onPlayerJoin(PlayerJoinEvent e) { UUID uuid = e.getPlayer().getUniqueID(); }

Eine bessere Datentabelle sähe also so aus: 1f50b0c928a8f69ed3b39cd8d920b3b4.png

In der ersten Spalte die uuid und in der zweiten Spalte die joins. Ja, jetzt sind es noch Zeilen, aber sobald die Tabelle Einträge hat, werden "uuid" und "joins" zu Spalten. Hier habe ich eine Datentabelle innerhalb von PHPMyAdmin erstellt. Eigentlich solltest du - egal wo du deine MySQL-Datenbank herhast, die Möglichkeit bekommen haben, PHPMyAdmin zu nutzen. Für die UUID habe ich hier einen 36-Stelligen varchar genommen, was auf den ersten Blick etwas komisch sein könnte. Aber natürlich kann sich MySQL nicht an alle Variablentypen anpassen, und mir extra die Möglichkeit geben, eine UUID zu speichern. Ein varchar ist vergleichbar mit einem String. Um die UUID in der Datenbank nutzen zu können, muss sie also erstmal zu einem String umgewandelt werden.

@EventHandler public void onPlayerJoin(PlayerJoinEvent e) { Player p = e.getPlayer(); UUID uuidUUID = p.getUniqueId(); String uuidString = uuidUUID.toString(); }

Normalerweise sollte man von der toString()-Methode absehen, wenn es denn möglich ist, und es beispielsweise Methoden wie getName() gibt. Das ist hier allerdings nicht der Fall. Jetzt kann noch einmal das ResultSet erstellt werden.

ResultSet rs = SQL.query("SELECT joins FROM minecraftforum WHERE uuid='" + uuidString + "';");

Die Methode gibt wie gesagt ein ResultSet zurück, das heißt, wir können es direkt in rs speichern und weiterverwenden. Wenn es keinen Eintrag für die UUID gibt, dann ist der Wert von rs null. Es muss also jetzt eine Abfrage getroffen werden, ob das ResultSet etwas beinhaltet, oder nicht.

if (rs.next()) { // Eintrag vorhanden}else { // Kein Eintrag vorhanden}

rs.next() gibt einen Boolean zurück, also true oder false. Wenn es einen Eintrag gibt, also auch ein "nächster" aufgerufen werden kann, so ist der boolean true, ansonsten ist er false. Vermutlich wird rs.next() bei dir rot unterstrichen. Du bekommst zwei Lösungsmöglichkeiten: Eine try-catch-"Sicherung" oder eine "throws" Declaration. Try- und Catch sind eine ganz famose Sache, weil man Errors damit super abfangen und beheben kann, aber für den Anfang wird es einfacher sein, von Eclipse die "throws" Declaration machen zu lassen. Später würde ich allerdings eher davon absehen und try-catch nutzen, vielleicht erklär' ich das ja auch noch in einem späteren Teil. Unser EventHandler sieht jetzt also so aus:

@EventHandler public void onPlayerJoin(PlayerJoinEvent e) throws SQLException { Player p = e.getPlayer(); UUID uuidUUID = p.getUniqueId(); String uuidString = uuidUUID.toString(); ResultSet rs = SQL.query("SELECT joins FROM minecraftforum WHERE uuid='" + uuidString + "';"); if (rs.next()) { // Eintrag vorhanden } else { // Kein Eintrag vorhanden } }

Nun müssen ja nur noch die verschiedenen Aktionen ausgeführt werden, abhängig davon, ob ein Eintrag vorhanden ist, oder nicht. Und dafür lässt sich super Arcalios update()-Methode nutzen.

if (rs.next()) { // Eintrag vorhanden int joins = 0; SQL.update("UPDATE minecraftforum SET joins='" + (joins + 1) + "' WHERE uuid='" + uuidString + "';"); }

Natürlich kann das so nicht klappen. Dem Spieler würde immer der Wert 1 bei joins zugewiesen werden, weil ich vorher joins auf 0 gesetzt habe. Denn vorher muss ja herausgefunden werden, wie viel Joins der Spieler schon hatte! Also, wir brauchen ein Ergebnis.

int joins = rs.getInt(1);

Das sieht jetzt mit Sicherheit etwas kompliziert aus, ist es aber gar nicht. rs wird in eine Integer umgewandelt, und die 1 als Parameter bei getInt() ist die Spalte. Das Schwerste ist geschafft. Es fehlt nur noch der Teil, der einen Eintrag erstellt, wenn noch keiner da war.

else { // Kein Eintrag vorhanden SQL.update("INSERT INTO " + db + " VALUES('" + uuidString + "', '1');");}

Wer ein bisschen Ahnung von MySQL hat sollte das schon verstehen. Es wird in der Datentabelle minecraftforum ein neuer Eintrag erstellt mit den Werten uuidString und 1. Macht Sinn, nicht? ;) Die ganze Klasse sieht jetzt also so aus, wobei ich meine richtigen Zugangsdaten mal rausgenommen habe ;)

package de.minecraftforum.meinpacket;import java.sql.ResultSet;import java.sql.SQLException;import java.util.UUID;import org.bukkit.entity.Player;import org.bukkit.event.EventHandler;import org.bukkit.event.Listener;import org.bukkit.event.player.PlayerJoinEvent;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin implements Listener { String host, db, user, pass, port; public void onEnable() { host = "censored"; db = "censored"; user = "censored"; pass = "censored"; port = "3306"; SQL.connect(host, db, user, pass, port); getServer().getPluginManager().registerEvents(this, this); } public void onDisable() { SQL.close(); } @EventHandler public void onPlayerJoin(PlayerJoinEvent e) throws SQLException { Player p = e.getPlayer(); UUID uuidUUID = p.getUniqueId(); String uuidString = uuidUUID.toString(); ResultSet rs = SQL.query("SELECT joins FROM minecraftforum WHERE uuid='" + uuidString + "';"); if (rs.next()) { // Eintrag vorhanden int joins = rs.getInt(1); SQL.update("UPDATE minecraftforum SET joins='" + (joins + 1) + "' WHERE uuid='" + uuidString + "';"); } else { // Kein Eintrag vorhanden SQL.update("INSERT INTO minecraftforum VALUES('" + uuidString + "', '1');"); } }}

Wie du sicher mitbekommen hast war bei jedem qry "minecraftforum". Das ist der Name der Datentabelle. Wenn du deine Datentabelle anders nennen willst, musst du das minecraftforum auch immer ersetzen. Wichtig ist, dass du beachtest, dass das Plugin nicht automatisch eine solche Datentabelle erstellt. Du musst es manuell machen. Wenn du willst, dass die Datentabelle automatisch angelegt wird, dann lies noch weiter, ansonsten verabschiede ich mich schonmal von dir und wünsche dir viel Spaß mit MySQL denn es öffnet viele, viele Türen. Automatisches Erstellen von Datentabellen:

Dafür brauchen wir in der onEnable()-Methode eine weitere Zeile, und zwar die Folgende:

"CREATE TABLE" ist ein Befehl von MySQL, so wie SELECT oder INSERT etc. IF NOT EXISTS stellt die Bedingung da - wenn eine solche Tabelle schon besteht, dann soll natürlich keine Neue angelegt werden. In den Klammern danach wird angegeben, was in der Datentabelle steht. Zuerst ein 36-stelliger varchar namens uuid und danach eine 10-stellige Integer namens joins. Natürlich muss das nach

passieren, sonst besteht ja noch keine Verbindung und dann kann auch nichts funktionieren wink.png

SQL.query("CREATE TABLE IF NOT EXISTS minecraftforum (uuid varchar(36), joins int(10));");
SQL.connect(host, db, user, pass, port);

Alles klar. Traut euch ruhig, mit euren Fragen hier aufzutreten! Mich würde es übrigens mal interessieren, wer tatsächlich bis hier hin mitgemacht hat, und was er / sie für Erfolge zu verbuchen hat! Liebe Grüße, Baustein 3bd1e46c15d6a6acbdbe8b062321a1b7.png Configs bieten dem Anwender eines Plugins die tolle Möglichkeit, aktiv Dinge im Plugin zu ändern. Ein jeder Plugin-Programmierer sollte mit ihnen umgehen können. Also, hauen wir rein:

package de.minecraftforum.meinpacket;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin {}

Sagen wir, wir definieren eine Nachricht, die einem Spieler ausgegeben werden soll, wenn er auf einen Eisenblock rechtsklickt. Dann haben wir auch gleich nen Kuchen im Plugin :) Also natürlich wird der Listener benötigt. Ich rushe da eben ein wenig durch und packe die nötigen Sachen rein und erkläre nicht viel, das haben wir in den vorigen Teilen schon zu genüge geübt.

package de.minecraftforum.meinpacket;import org.bukkit.event.EventHandler;import org.bukkit.event.Listener;import org.bukkit.event.player.PlayerInteractEvent;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin implements Listener { public void onEnable() { getServer().getPluginManager().registerEvents(this, this); } @EventHandler public void onRightClickIronBlock(PlayerInteractEvent pie) { }}

Die Nachricht, die ausgegeben wird soll natürlich in einer Config festgelegt werden können. Also, machen wir doch erstmal, dass eine Nachricht an einen User ausgegeben wird. Das brauch ich auch nicht groß zu Erklären, denn mit dem Know-How aus den letzten Parts dürfte das bei Niemandem Probleme bereiten.

@EventHandlerpublic void onRightClickIronBlock(PlayerInteractEvent pie) { if (pie.getAction() == Action.RIGHT_CLICK_BLOCK) { Player p = pie.getPlayer(); p.sendMessage(""); // Nachricht aus Config } }

Und schon sind wir bei der Config. Es macht Sinn, eine Methode zu erstellen, um die Config zu laden bzw. Werte aus ihr zu entnehmen. Sie gibt nichts zurück, also ist sie natürlich void. Sie braucht auch keine Parameter.

public void loadConfig() { }

Wie kommen wir an die Config... Bukkit liefert da eine schöne Methode mit. Und zwar die Methode getConfig(). getConfig() gibt eine Variable des Typs FileConfiguration zurück, also können wir sie speichern.

public void loadConfig() { FileConfiguration cfg = this.getConfig();}

Und wie auch beispielsweise bei "p" des Typs "Player" hat die FileConfiguration eine Menge hilfreicher Methoden. Im Folgenden nutze ich die addDefault()-Methode, mit der man einen Eintrag in der config.yml erstellen kann.

cfg.addDefault(path, value)

Sowohl path als auch value sind Strings. Der Path ist... der Pfad, wer hätte es gedacht. Wenn ich bei path "Message" und bei value "Hihihi du siehst mich nicht" eingebe, dann wird meine Config so aussehen:

Message: 'Hihihi du siehst mich nicht'

Wenn ich bei path "Settings.Message" und bei Value "Hihihi du siehst mich nicht" eingebe, dann wird meine Config so aussehen:

Settings: Message: 'Hihihi du siehst mich nicht'

Ich mache für dieses Beispiel mal die Nachricht "?2You clicked an iron block."

public void loadConfig() { FileConfiguration cfg = this.getConfig(); cfg.addDefault("Message.Iron Block", "?2You clicked an iron block.");}

Und um das Beispiel noch ein wenig anschaulicher zu machen, mache ich noch eine Nachricht für einen Gold-Block:

public void loadConfig() { FileConfiguration cfg = this.getConfig(); cfg.addDefault("Message.Iron Block", "?2You clicked an iron block."); cfg.addDefault("Message.Gold Block", "?2You clicked a golad block.");}

Nun müssen wir der Config noch sagen, dass sie sich selber überschreiben soll, wenn sie fehlerhaft bzw. leer ist oder gar nicht existiert. Dafür gibt es die Methode copyDefaults(boolean value) die eine options() von cfg ist.

cfg.options().copyDefaults(true);

Damit sich danach die Config auch speichert, machen wir

this.saveConfig();

Jetzt weiß das Plugin blöderweise noch nicht, was in der Config steht. Dafür müssen wir wieder Daten aus der Config heraus "ziehen" anstatt sie "reinzutun". Das geht mit der Methode getString() oder getInt() etc.:

String ironBlock = cfg.getString("Message.Iron Block");String goldBlock = cfg.getString("Message.Gold Block");

Als Parameter gibt's wieder den Pfad, damit auch die Info von der richtigen Stelle geholt wird. In "ironBlock" ist jetzt also der Inhalt der Konfiguration bei "Message.Iron Block" und für "goldBlock" dasselbe für "Message.Gold Block". Natürlich macht es Sinn, wenn wir diese Strings für die ganze Klasse zugänglich machen und sie sich nicht nur in der loadConfig()-Methode befinden.

public class MeineKlasse extends JavaPlugin implements Listener { private String ironBlock, goldBlock; public void onEnable() { getServer().getPluginManager().registerEvents(this, this); } @EventHandler public void onRightClickIronBlock(PlayerInteractEvent pie) { if (pie.getAction() == Action.RIGHT_CLICK_BLOCK) { Player p = pie.getPlayer(); p.sendMessage(""); // Nachricht aus Config } } public void loadConfig() { FileConfiguration cfg = this.getConfig(); cfg.addDefault("Message.Iron Block", "?2You clicked an iron block."); cfg.addDefault("Message.Gold Block", "?2You clicked a golad block."); cfg.options().copyDefaults(true); this.saveConfig(); this.ironBlock = cfg.getString("Message.Iron Block"); this.goldBlock = cfg.getString("Message.Gold Block"); } }

Einige dürften diesen Schritt schon selbst vorgenommen haben; die loadConfig()-Methode sollte in der onEnable() aufgerufen werden, damit die Config bei jedem Serverstart / -reload neu geladen wird. Natürlich könnte man die Methode auch mit einem Kommando wie beispielsweise /config reload neu laden.

public void onEnable() { loadConfig(); getServer().getPluginManager().registerEvents(this, this);}

Jetzt muss noch beim Kuchen abgefragt werden, ob ein Eisenblock oder ein Goldblock angeklickt wurde. Hat nichts mit der Config zu tun, also hier ist der Code: ;)

@EventHandlerpublic void onRightClickIronBlock(PlayerInteractEvent pie) { if (pie.getAction() == Action.RIGHT_CLICK_BLOCK) { Player p = pie.getPlayer(); if (pie.getClickedBlock().getType() == null) return; else if (pie.getClickedBlock().getType() == Material.IRON_BLOCK) { p.sendMessage(ironBlock); } else if (pie.getClickedBlock().getType() == Material.GOLD_BLOCK) { p.sendMessage(goldBlock); } } }

Zuerst machen wir den Nullcheck, um NPEs zu vermeiden. Danach schauen wir, ob der angeklickte Block ein Gold- oder Eisenblock war, und wenn ja, dann wird die entsprechende Nachricht zugeteilt. Also, versuchen wir's! f32c964c0189c867fd624c82c37af4b6.png Und für den Gold-Block: edbbd5fbf5693b12c6be8296b29c647e.png Oke, der "golad"-Block war vielleicht ein Fail. Aber Kopf hoch! Das lässt sich ja jetzt in der Config.yml ändern! Denn diese hat sich jetzt automatisch im plugins-Ordner erstellt. 6762d7180fa96e42b01170eb6a555bbb.pnga68402d4a3f992b367a37fe9f0e47b97.png Und mal sehen.... 2208c4c4fa5a73086f4cab862bd0f71a.png Super! Ich wünsche euch natürlich wieder viel Spaß mit den Configs :) Liebe Grüße, Baustein 900b457bed7af5ecbc5bc9f4ee2cb238.png Auch, wenn das hier eigentlich kein Java-How-To werden soll, folgt ein Teil, der wohl doch eher in die Kategorie einsortiert werden könnte. Es geht um Try und Catch. Ein jeder Programmierer hat schonmal nervige Errors bekommen. Manchmal bedeuten diese Errors gar nichts schlimmes! Ein Beispiel: Der Spieler gibt ein Kommando ein: /parse 1234567. Er soll jetzt eine Nachricht bekommen, nämlich "1234567". Das erste Argument seines Kommandos muss eine Zahl sein. Uns beim Backend wird ein Argument allerdings als String übergeben, denn genau das ist es: Der Spieler könnte ja dreist wie er ist auch /parse 1a2a3a4a5a6a7a eingeben. Um aus einem String eine Integer zu bekommen, gibt es diese Methode:

Integer.parseInt(String s);

Doch wenn der String nicht numerische Zeichen enthält, dann wird ein Error-Log ausgegeben. Eine "NumberFormatException" wird "geworfen". Aber diese Exception bedeutet ja nicht, dass wir Mist zusammenprogrammiert haben. Wir können diese Exception sogar sinnvoll nutzen. Und das ermöglicht uns Try & Catch. Ein Try & Catch-"Absatz" ist immer wie folgt aufgebaut:

try { // Code falls alles glatt läuft} catch (Exception e) { // Code falls eine Exception auftritt}

Der Parameter von catch ist eine Exception und ihr Name, den wir im Folgenden benutzen wollen. Statt "e" kannst du also machen, was du willst. Es ist eher unschön, eine "Exception" aufzufangen. Es gibt nämlich ganz viele verschiedene Arten von Exceptions und damit wird jede einzelne so abgefangen. Vielleicht wird aber eine Exception geworfen, die gar nicht am User, sondern tatsächlich an unserem Code liegt, und dann ist ja nicht der Sinn der Sache, dass der User angekreidet wird, er hätte unser Kommando falsch verwendet. Daher gibt man statt "Exception" eigentlich etwas Genaueres an, wie beispielsweise "SQLException" oder eben "NumberFormatException". Ich fülle mal eben den Try-Teil aus und erstelle das Kommando:

@Overridepublic boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { if (cmd.getName().equalsIgnoreCase("parse")) { int argument; try { // Code falls alles glatt läuft argument = Integer.parseInt(args[0]); } catch (Exception e) { // Code falls eine Exception auftritt } } return true;}

Wie gesagt, eine NumberFormatException wird geworfen, wenn keine Integer geparst werden konnte, was meist (!) daran liegt, dass nicht numerische Charakter im angegebenen String enthalten waren. Also lässt sich der Catch-Teil verbessern:

} catch (NumberFormatException e) { // Code falls eine Exception auftritt}

Jetzt müssen wir uns nur noch überlegen, was mit der Exception angestellt werden soll. Was auf jeden Fall oft fürs Testen sinnvoll ist, ist den Error-Log auszugeben. Dafür gibt es die Methode printStackTrace().

} catch (NumberFormatException e) { // Code falls eine Exception auftritt e.printStackTrace();}

wäre also eine Möglichkeit, doch das würde dem User ja nicht helfen, es würde ihn eher verunsichern, obwohl wir genau an dieses Problem gedacht haben! Trotzdem sollte man die Methode immer im Hinterkopf behalten, denn wie gesagt - für's Testen ist sie perfekt. Stattdessen sollten wir dem Kommando-Sender ausgeben, was er beim nächsten Mal besser machen sollte.

} catch (NumberFormatException e) { // Code falls eine Exception auftritt sender.sendMessage(args[0] + " is not a number.");}

Das ist schon alles, was du für dieses eine Beispiel brauchst. Manchmal ist es jedoch wichtig, etwas zu tun, egal, ob ein Fehler auftritt, oder nicht. Dafür gibt es finally.

try { // Code falls alles glatt läuft argument = Integer.parseInt(args[0]); } catch (NumberFormatException e) { // Code falls eine Exception auftritt sender.sendMessage(args[0] + " is not a number.");} finally { // Code der immer ausgeführt wird sender.sendMessage("Thanks for using my plugin.");}

Was auch hier nicht schön ist, und wo Eclipse auch wieder rummoppert: "return" im finally-Block. Davon lieber absehen ;) In manchen Fällen kann es auch passieren, dass mehrere Exceptions auftreten können und man auf alle unterschiedlich reagieren möchte. Dafür habe ich dir das folgende Beispiel mit der NumberFormatException und der NullPointerException.

} catch (NullPointerException e) { // Code falls eine NullPointerException auftritt}

Die ganze Klasse sieht jetzt also so aus:

package de.minecraftforum.meinpacket;import org.bukkit.command.Command;import org.bukkit.command.CommandSender;import org.bukkit.event.Listener;import org.bukkit.plugin.java.JavaPlugin;public class MeineKlasse extends JavaPlugin implements Listener { @Override public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) { if (cmd.getName().equalsIgnoreCase("parse")) { int argument; try { // Code falls alles glatt läuft argument = Integer.parseInt(args[0]); sender.sendMessage(argument + ""); } catch (NumberFormatException e) { // Code falls eine NumberFormatException auftritt sender.sendMessage(args[0] + " is not a number."); } catch (NullPointerException e) { // Code falls eine NullPointerException auftritt } finally { // Code der immer ausgeführt wird sender.sendMessage("Thanks for using my plugin."); } } return true; } }

(Ich habe noch im Try-Block den eigentlich auszuführenden Teil angefügt. Das + "" ist nötig, weil argument eine Integer ist und Eclipse da wieder meckert, wenn man die Integer nicht an einen String anfügt.) Ich vertraue dir mal, dass du es schaffst, das Kommando in der plugin.yml zu registrieren. Dann geht's jetzt an's Testen! 3f0bb36f7e377c3d1c1ebf3b136a572c.png Wenn ich also tatsächlich eine Zahl übergebe, so werden der Try- und der Finally-Teil ausgeführt. 7f482f28219c8d6998f94fff2016be37.png Wenn mein Argument nicht numerische Zeichen enthält, so werden der erste Catch- und der Finally-Teil ausgeführt. 39acefad1b19c9fc91333122d53cef91.png Waaas? Ein Error-Log? Ja, und zwar aus einem bösen Grund: Ich habe wie man in der ersten Zeile sieht einfach nur /parse eingegeben, und das ohne Argumente. Eine ArrayIndexOutOfBoundsException wird geworfen. Und das ist meine "Quest" an dich "tapferen Recken"! Mach eine Try- & Catch-Abfrage, um das zu verhindern! Ja, sicher wäre es besser, einfach nach der Länge der Argumente mit args.length zu sehen, aber Übung macht den Meister! Liebe Grüße, Baustein

Link zu diesem Kommentar
Auf anderen Seiten teilen

  • 1 month later...

b5ee25a4cb.png

Es ist mal wieder Zeit für einen neuen Teil des How-To über das Programmieren von Bukkit Plugins. Heute versuche ich, dir näherzubringen, wie du Scheduler in deine Plugins einbauen kannst. Aber erstmal: Was machen und sind Scheduler? Tuesday_29_07_2014_17:17:07.png Vermutlich ist die erste Übersetzung des "Planers" die Beste. Der Scheduler erlaubt dir, verschiedene Aufgaben zu "planen". Diese Aufgaben können alles sein, einen Countdown starten oder etwas im Chat ausgeben lassen. Dabei wird zwischen den verschiedenen Aufgabentypen unterschieden. Sync und Async oder Synchronous und Asynchronous. Tuesday_29_07_2014_17:22:17.pngTuesday_29_07_2014_17:22:45.png Den Unterschied zwischen einem SyncTask und einem AsyncTask erklärt dir im Folgenden TuxGamer:

Minecraft-Server benutzen einen Thread. Das bedeutet, dass alles, was passiert, nacheinander abläuft. Zuerst werden Plugindaten berechnet, danach Tiere und so weiter. Wenn das Fertig ist, wird gewartet. Der beschriebene Prozess nennt man einen Tick. Normalerweise haben Server 20 Ticks pro Sekunde. Das reicht aus, um so wenig Ressourcen wie möglich zu verwenden und ist ideal für ein Laggfreies Spiel. Doch was passiert, wenn vorrauszusehen ist, dass eine Aufgabe so lange braucht, dass sie den Server laggen würde? Dann erstellt man einen sogenannten Thread, der eine Aufgabe ausführt, die so lang dauern kann wie sie will, und dabei den Server nicht laggt. Doch nicht alles kann man auf diese Weise, auch asynchron genannt, ausführen. So sind Dinge, die mit Tieren in einer Welt arbeiten, nicht möglich, sie müssen im Main Thread, also synchron, ausgeführt werden. Zu beachten ist auch, dass das Erstellen eines Threads für etwas Asynchrones selber nochmal seine Zeit benötigt. Wenn also die Aufgabe relativ lang dauert, aber weniger als die Erstellung eines Threads, dann sollte man eher einen synchronen Task verwenden. Die Erstellung eines Threads benötigt eh nur wenige Millisekunden und ruft daher keine großen TPS-Veränderungen hervor.

Nun wird dann noch erneut zwischen RepeatingTask und DelayedTask unterschieden. Die Namen sprechen auch hier für sich, RepeatingTasks stellen etwas dar, was sich immer weiter wiederholt, bis der Task abgebrochen wird, und DelayedTasks etwas, was erst nach einer festgelegten Wartezeit ausgeführt wird. Wir machen ein schönes Beispiel: Zwei Kommandos. Ein Kommando /repeat und ein Kommando /delay. /repeat soll dazu führen, dass der User, der das Kommando ausführt, alle 3 Sekunden einen Diamanten erhält. Wenn er ein weiteres mal /repeat eingibt, so soll die Diamantenflut ihr Ende finden. /delay gibt ihm einen Smaragd. Danach kann der User aber 5 Sekunden lang nicht mehr /delay nutzen, bzw. bekommt eine Nachricht, die ihm besagt, dass er noch im Cooldown steckt. Also 'ran an den Speck! Im Folgenden nochmal die Grundstruktur für die Erstellung des Plugins, die mitterweile aber jedem fleißigen Leser geläufig sein dürfte:

package de.minecraftforum.scheduler;import org.bukkit.plugin.java.JavaPlugin;public class Main extends JavaPlugin {}

Da zwei Kommandos gebraucht werden, ist natürlich eine onCommand-Methode von Nöten. Diese habe ich schon im Teil "Kommandos" erklärt, wen's interessiert ;)

public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {	// Zeugs	return true;}

Die Abfrage, wie das Kommando heißt bzw. alles was dazu gehört, lässt sich auch im o.g. Teil nachschlagen, also überspringe ich das Geblubber:

public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {	if (!(sender instanceof Player)) { // Wenn der Absender des Kommandos kein Spieler ist --> Konsole		sender.sendMessage("This command cannot be entered in console!");		return true;	}		Player p = (Player) sender;				if (command.getName().equalsIgnoreCase("repeat")) {			}		else if (command.getName().equalsIgnoreCase("delay")) {			}		return true;}

So. Jetzt geht's ans Eingefleischte! Zuerst das Repeat-Kommando. Ich bau's auf und erläre es danach :)

private void diamantenGeben() {	Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {		@Override		public void run() {					}	}, 0L, 3 * 20L);}

Der Übersichtlichkeit halber habe ich es in eine eigene Methode namens diamantenGeben() gepackt.

Bukkit.getScheduler() - daran musst du dich einfach gewöhnen. Die .scheduleBLA-Methoden sind halt Methoden für den Variablentyp Scheduler, und den müssen wir ja irgendwie bekommen. Dann sage ich, dass ich einen syncRepeatingTask haben will, denn das Vergeben eines Items an ein paar Spieler braucht kaum Leistung und die Erstellung eines weiteren Threads würde da länger dauern. Repeating, weil das, was jetzt bei dem Krassen Kram steht, natürlich mehrfach ausgeführt werden soll, im Grunde so lang, bis der Spieler sein Kommando zurücknimmt. Die Methode scheduleSyncRepeatingTask() verlangt einige Parameter von uns.

  • Plugin arg0 Hier musst du die Main-Klasse deines Plugins angeben, also die Klasse, die du in der plugin.yml bei "Main" gesetzt hast. In diesem Fall ist es "this", denn auch in diesem Teil wird sich alles in der Main-Klasse abspielen.
  • Runnable arg2 Eine Runnable ist der Teil, der bei @Override anfängt und mit der geschweiften Klammer unter @Override aufhört. Dazu gleich mehr.
  • long arg2 Eine Variable des Typs long ist eigentlich einfach eine Zahl. Sie deckt alle Zahlen zwischen -9223372036854775808 und 9223372036854775807 ab. Der Kennbuchstabe einer Long-Variable ist das L, ob groß oder klein ist egal, jedoch sollte man immer das große L nutzen, weil das kleine manchmal mit einer 1 oder dem großen I zu verwechseln ist. Diese erste Zahl ist die Zeit, die vergehen soll, bevor unsere Runnable ausgeführt wird. In diesem Fall soll die Runnable direkt ausgeführt werden, und daher gibt es hier 0L.
  • long arg3 Dieser zweite Parameter des Typs long ist die Zeit, die vergehen soll, bevor die Runnable erneut ausgeführt wird. Wie oben gesagt soll der User alle 3 Sekunden einen Diamanten bekommen, daher grundsätzlich erstmal die 3. Aber was macht das * 20 da? Die letzten beiden Angaben werden in Ticks gelesen. Eine Sekunde sind 20 Ticks. Anstatt jetzt 60L zu schreiben ist es oft schöner X * 20L zu schreiben und X durch die beliebige Sekundenanzahl zu ersetzen.

Jetzt passiert etwas, was ein YouTuber namens SgtCaze, der Bukkit Plugin Development Tutorials macht, und den ich sehr mag, die "voidception" nennt. Denn in der onCommand-Methode wird jetzt eine neue Methode gemacht!? Diese Methode muss run heißen! Nur dann wird erkannt, dass alles, was in ihr steht der Teil ist, der häufiger ausgeführt werden soll.

Jetzt fehlt noch das Geben des Diamanten an den Spieler. Da das nicht viel mit dem Thema zu tun hat rushe ich da auch durch.

@Overridepublic void run() {	for (Player p : Bukkit.getOnlinePlayers()) p.getInventory().addItem(new ItemStack(Material.DIAMOND, 1));}

Hier gebe ich jedem Spieler auf dem Server einen Diamanten. Dieses for-Konstrukt nennt sich for-each-Schleife. Ich speichere jeden Spieler, den mit Bukkit.getOnlinePlayers() zurückgibt einmal in der Variable p und führe dafür den Code aus, der dahinter steht, nämlich das p.getInventory().addItem(Bla).

Zu den ItemStacks werde ich später noch einen Teil des How-Tos machen, vorerst werde ich sie aber nicht erklären.

Auf jeden Fall klappt jetzt schon die Item-Vergabe. Aber wenn der User erneut /repeat eingibt, so wird sich nichts deaktivieren, nein, viel besser noch, alle werden doppelt so viele Dias in derselben Zeit bekommen, weil jetzt mehrere Tasks für laufen!

Daher sollten wir eine kleine boolean-Abfrage machen. Ich bin mir ziemlich sicher, dass ich die boolean-Variablen schon in einem früheren Teil erklärt habe, aber es kann nicht schaden, das hier zu wiederholen.

Booleans haben entweder den Wert true, oder false (oder natürlich null).

Ich mache einen boolean mit dem Namen diamantenSollenVergebenWerden. Diesen setze ich standardmäßig einfach mal auf false.

Diese Variable sollte bestenfalls noch vor der onCommand-Methode stehen, damit sie für immer gilt, und nicht nur innerhalb einer Methode.

public class Main extends JavaPlugin {	boolean diamantenSollenVergebenWerden = false;			public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {		if (!(sender instanceof Player)) { // Wenn der Absender des Kommandos kein Spieler ist --> Konsole			sender.sendMessage("This command cannot be entered in console!");			return true;		}				final Player p = (Player) sender;								if (command.getName().equalsIgnoreCase("repeat")) {					}				else if (command.getName().equalsIgnoreCase("delay")) {					}				return true;	}		private void diamantenGeben() {		Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {			@Override			public void run() {				for (Player p : Bukkit.getOnlinePlayers()) p.getInventory().addItem(new ItemStack(Material.DIAMOND, 1)); 			}		}, 0L, 3 * 20L);	}}

Jetzt muss in der onCommand-Methode der Wert von diesem boolean immer getauscht werden. Das geht bei booleans ganz einfach mit

diamantenSollenVergebenWerden = !diamantenSollenVergebenWerden

Das Ausrufezeichen steht dafür, dass etwas umgekehrt wird. Wenn bei einer if-Abfrage die Bedingung (!(sender instanceof Player)) ist, so wird der nachgestellte Teil nur ausgeführt, wenn der sender keine Instanz des Typs Player ist!

Wenn bei

diamantenSollenVergebenWerden = !diamantenSollenVergebenWerden

jetzt auf der linken Seite true steht, so steht auf der rechten Seite !true, also false. Wenn aber auf der linken Seite false steht, so steht rechts !false, also true.

Mag jetzt erstmal verwirren, aber mit der Zeit gewöhnt man sich dran ;)

Außerdem muss natürlich noch gegebenenfalls die neu gemachte Methode aufgerufen werden. Mit gegebenenfalls meine ich, dass die Methode natürlich nur dann ausgeführt werden soll, wenn diamantenSollenVergebenWerden = true ist. Die onCommand-Methode sieht also jetzt so aus:

public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {	if (!(sender instanceof Player)) { // Wenn der Absender des Kommandos kein Spieler ist --> Konsole		sender.sendMessage("This command cannot be entered in console!");		return true;	}		final Player p = (Player) sender;				if (command.getName().equalsIgnoreCase("repeat")) {		diamantenSollenVergebenWerden != diamantenSollenVergebenWerden;		if (diamantenSollenVergebenWerden) diamantenGeben();	}		else if (command.getName().equalsIgnoreCase("delay")) {			}		return true;}

Jetzt muss der RepeatingTask auch beendet werden, wenn /repeat erneut eingegeben wird. Dafür brauchen wir eine ID. Keine Block- oder Item-ID, sondern die ID des Tasks. Die zu bekommen ist nicht schwer.

Bukkit.getScheduler().scheduleIrgendeinenTask(Parameter);

liefert nämlich eigentlich einen Integer, nur dass wir diesen noch nirgendwo speichern. Das lässt sich aber ändern. Zu dem boolean kommt jetzt also auch noch ein int id:

boolean diamantenSollenVergebenWerden = false;int id;

Und beim Erstellen des Tasks belegen wir die ID:

id = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {

Zurück in die onCommand-Methode:

if (command.getName().equalsIgnoreCase("repeat")) {	diamantenSollenVergebenWerden = !diamantenSollenVergebenWerden;	if (diamantenSollenVergebenWerden) diamantenGeben();	else Bukkit.getScheduler().cancelTask(id);}

das tut's. Wenn diamantenSollenVergebenWerden false ist, so wird der "else"-Teil aufgerufen, und der Task wird gekenzelt.

Puh, das wäre geschafft. Also dann ab zum DelayedTask.

Eine weitere Methode und ein weiterer boolean wird benötigt. Der boolean besagt, ob gerade der Cooldown aktiv ist, oder nicht, und die Methode setzt und nimmt den Cooldown.

boolean diamantenSollenVergebenWerden = false;int id;boolean momentanImCooldown = false;

Die Methode nenne ich schlicht und einfach cooldown.

private void cooldown() {	}

Das erste, was diese Methode tun wird, ist es, momentanImCooldown auf true zu setzen.

private void cooldown() {	momentanImCooldown = true;}

Danach soll besagte 5 Sekunden gewartet werden, bis momentanImCooldown wieder auf false gesetzt wird.

Der Aufbau hier ist sehr ähnlich zum SyncRepeatingTask:

Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() {				@Override	public void run() {							}}, 5 * 20L);

Hier brauchst du bloß einen Zeit-Parameter anzugeben, und zwar die Zeit, die gewartet werden soll, bevor das in der run()-Methode ausgeführt wird. Hier also 5 * 20L = 5 * 20 Ticks = 100 Ticks = 5 Sekunden.

Was ausgeführt werden soll? Hab ich schon gesagt, momentanImCooldown soll wieder false sein.

@Overridepublic void run() {	momentanImCooldown = false;		}

Und wieder in die onCommand-Methode:

else if (command.getName().equalsIgnoreCase("delay")) {	if (momentanImCooldown) p.sendMessage("Du bist noch im Cooldown!");	else {		cooldown();		for (Player op : Bukkit.getOnlinePlayers()) {			op.getInventory().addItem(new ItemStack(Material.EMERALD, 1));		}	}}

Wenn momentanImCooldown true ist, so bekommt der Spieler eine Nachricht, die ihm das erklärt. Ansonsten wird cooldown() aufgerufen und jeder Spieler bekommt 'nen Emerald.

Der gesamte Code sieht jetzt also so aus:

package de.minecraftforum.scheduler;import org.bukkit.Bukkit;import org.bukkit.Material;import org.bukkit.command.Command;import org.bukkit.command.CommandSender;import org.bukkit.entity.Player;import org.bukkit.inventory.ItemStack;import org.bukkit.plugin.java.JavaPlugin;public class Main extends JavaPlugin {	boolean diamantenSollenVergebenWerden = false;	int id;		boolean momentanImCooldown = false;		public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {		if (!(sender instanceof Player)) { // Wenn der Absender des Kommandos kein Spieler ist --> Konsole			sender.sendMessage("This command cannot be entered in console!");			return true;		}				final Player p = (Player) sender;								if (command.getName().equalsIgnoreCase("repeat")) {			diamantenSollenVergebenWerden = !diamantenSollenVergebenWerden;			if (diamantenSollenVergebenWerden) diamantenGeben();			else Bukkit.getScheduler().cancelTask(id);		}				else if (command.getName().equalsIgnoreCase("delay")) {			if (momentanImCooldown) p.sendMessage("Du bist noch im Cooldown!");			else {				cooldown();				for (Player op : Bukkit.getOnlinePlayers()) {					op.getInventory().addItem(new ItemStack(Material.EMERALD, 1));				}			}		}				return true;	}		private void diamantenGeben() {		id = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {			@Override			public void run() {				if (!diamantenSollenVergebenWerden) 				for (Player p : Bukkit.getOnlinePlayers()) p.getInventory().addItem(new ItemStack(Material.DIAMOND, 1)); 			}		}, 0L, 3 * 20L);	}		private void cooldown() {		momentanImCooldown = true;		Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() {						@Override			public void run() {				momentanImCooldown = false;					}		}, 5 * 20L);	}}

Damit ist eigentlich auch schon alles fertig. Nur die plugin.yml muss noch für die neuen Kommandos geändert werden:

name: Minecraftforumversion: 1.0author: Bausteinmain: de.minecraftforum.scheduler.Maincommands:  repeat:    usage: /repeat  delay:    usage: /delay

Na dann, exportieren und ab dafür!

Mein erster Test hat ergeben, dass /delay funktioniert, /repeat aber nicht!?

Was soll's dann kommt jetzt auch noch die Fehlersuche ... ^^

... und diese hat ergeben, dass ich bei diamantenGeben() noch eine falsche if-Abfrage drin hatte, weil ich beim Schreiben überlegt hatte, das Ganze anders zu lösen. Die muss raus.

private void diamantenGeben() {	id = Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() {		@Override		public void run() {			for (Player p : Bukkit.getOnlinePlayers()) p.getInventory().addItem(new ItemStack(Material.DIAMOND, 1)); 		}	}, 0L, 3 * 20L);}

Und schon funktioniert's ;)

Dieses Plugin verdeutlicht die Wirkung und Funktionsweise von Schedulern, hat aber seine Macken. Jeder Spieler kann für jeden Spieler die Tasks starten und stoppen. Um es für alle Spieler einzeln zu machen, würden wir aber HashMaps oder ArrayLists benötigen, für die es noch zu früh ist.

Falls dir dieser Teil der How-To-Reihe gefallen hat, so würde ich mich natürlich sehr darüber freuen, wenn du auf "Gefällt mir" am unteren rechten Beitragsrand klickst. Auch darfst du dich auf diesem Wege gerne bei TuxGamer bedanken, der ganz zufällig eine Antwort auf diesen Post abgegeben hat.

Liebe Grüße,

Baustein

Link zu diesem Kommentar
Auf anderen Seiten teilen

Hui, ich sehe mich selber :3

Kleine Anmerkung bzgl asynchronen Schedulern: Ihr braucht das im Moment noch nicht, das wird dann bei Datenbankzugriffen wichtig. Ihr hattet zwar schon MySQL, aber ich nehme nicht an, dass bereits die Datenmengen so groß sind, dass sie den Server zum laggen bringen würden.

Viel Spaß beim Weiterlesen, Tuxi

Link zu diesem Kommentar
Auf anderen Seiten teilen

433cec6159.png

Die Überschrift ist "ItemStacks", also lass uns über... ItemStacks reden!

Bei ItemStacks ist der Name Programm. Den Teil des "Item"s kennen wir alle, das sind die Dinger, die du immer im Inventar hast. "Stack" heißt Stapel. Macht Sinn, schließlich ist es ja ziemlich unschön, Dinge einfach so in den Rucksack zu pfeffern, da stapelt man sie lieber und hat gleich viel mehr Platz.

Du kannst dir sicher vorstellen, dass ItemStacks in den verschiedensten Bereichen bei der Entwicklung von Bukkit Plugins benötigt werden. Ein Beispiel hatte ich ja schon im letzten Teil bei den Schedulern gehabt!

Im Folgenden werde ich dir also die genaue Umgehensweise mit ItemStacks erklären. Dazu erzeugen wir einen ItemStack und schreiben danach eine Methode, die du immer in deine Plugins kopieren kannst, wenn du einen ItemStack brauchst.

Das Grundgerüst ist mittlerweile bekannt:

package de.minecraftforum.itemstacks;import org.bukkit.plugin.java.JavaPlugin;public class Main extends JavaPlugin {	}

Ich schreibe schnell eine onCommand()-Methode, die auf das Plugin "item" hört.

package de.minecraftforum.itemstacks;import org.bukkit.command.Command;import org.bukkit.command.CommandSender;import org.bukkit.entity.Player;import org.bukkit.plugin.java.JavaPlugin;public class Main extends JavaPlugin {	public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {		if (!(sender instanceof Player)) {			sender.sendMessage("Das hast du doch nicht in der Konsole einzugeben! Also hör mal! :c");			return true;		}				if (cmd.getName().equalsIgnoreCase("item")) {			// Stuff		}		return true;	}}

Da wo jetzt //stuff steht werde ich einen ItemStack erstellen, und dir dann seinen Aufbau erklären.

if (cmd.getName().equalsIgnoreCase("item")) {	ItemStack item = new ItemStack(Material.BREAD, 21);}

ItemStack item = new ItemStack() sollte erstmal klar sein. Wir erstellen eine neue Variable des Typs ItemStack namens "item". Mit new ItemStack() rufen wir den Konstruktor aus der Klasse ItemStack auf. Das ist erstmal schwer zu raffen, aber für den Anfang reicht es, wenn du es mir glaubst.

Dieser Konstruktor ist eigentlich nichts anderes als eine Methode. Mit ihm kannst du ein Objekt von einer Klasse "konstruieren". Java ist eine objektorientierte Programmiersprache, daher wirst du sehr viel mit solchen Konstruktoren arbeiten (müssen).

Ein Konstruktor kann genauso wie eine "normale Methode" Parameter haben. Muss er nicht, kann er aber. Der ItemStack hat gleich mehrere Konstruktoren:

Wednesday_30_07_2014_22:00:41.png

In den Klammern siehst du hier immer die Parameter, die an den Konstruktor übergeben werden müssen. Den zweiten Konstruktor lassen wir im Folgenden mal unter den Tisch fallen.

Wie du siehst heißt der erste Parameter immer "type". Das hat auch einen guten Grund, haha! Denn dieser Parameter gibt immer den Typ des Items an. Ist es ein Cobblestone? Ist es ein Eimer Milch? Das kann entweder in Form einer Integer, also einer ganzzahligen Zahl, oder in Form eines Materials geschehen. Materialien sind leichter, aber dazu gleich mehr ;)

Der zweite Parameter ist immer "amount". "amount" heißt auf Deutsch "Menge" oder "Anzahl". Das ist immer eine Integer, also immer eine ganzzahlige Zahl, was Sinn macht, da du ja nur ungerne 1,5 Leitern hast.

Der dritte Parameter - sofern es den im genutzten Konstruktor überhaupt noch gibt - heißt "damage" und gibt somit den Schaden des Items an. Damit kannst du beispielsweise einer Spitzhacke zu erkennen geben, dass sie nicht mehr ganz ist, oder die Farbe von Wolle ändern.

Und beim vierten Parameter... da muss ich passen, ich hab keine Ahnung ^^ Aber vielleicht meldet sich ja wer hier drauf, der das weiß.

Nun gilt es, den Konstruktor herauszusuchen, der für unser Ziel am besten passt. Ich hatte oben im Beispiel den fünften Konstruktor mit den Parametern der Typen Material und Integer genutzt.

Wenn du nur Material. eingibst, dann solltest du eine Auswahl aller Materialien zu sehen bekommen. Sollte dem nicht so sein, drück einfach einmal Strg + die Leertaste.

Als Menge habe ich 21 eingetippt.

Jetzt geht's auch schon daran, mit ItemStacks umzugehen. Ich schreibe wieder ein, zwei Zeilen.

if (cmd.getName().equalsIgnoreCase("item")) {	ItemStack item = new ItemStack(Material.BREAD, 21);	Player p = (Player) sender;	p.getInventory().addItem(item);}

Die Methode addItem(), die es in der Klasse PlayerInventory gibt, braucht nämlich einen ItemStack als Parameter!

Ein anderes Beispiel: Lass uns checken, ob der Spieler vielleicht schon diesen ItemStack in seinem Inventar hat, bevor er ihn von uns bekommt!

if (cmd.getName().equalsIgnoreCase("item")) {	ItemStack item = new ItemStack(Material.BREAD, 21);	Player p = (Player) sender;	if (p.getInventory().contains(item)) {		p.sendMessage("Das hast du doch schon!");		return true;	}	else {		p.getInventory().addItem(item);	}}

Denn auch die Methode contains() benötigt einen ItemStack.

Um weiterzumachen werde ich aber die neue Änderung rückgängig machen:

if (cmd.getName().equalsIgnoreCase("item")) {	ItemStack item = new ItemStack(Material.BREAD, 21);	Player p = (Player) sender;	p.getInventory().addItem(item);}

Denn jetzt zeig' ich dir eine Methode. Und die hat es in sich! Denn mit ihr kannst du in jedem deiner Plugins Items machen, mit einer beliebigen Anzahl, einem beliebigen Namen und einer beliebigen Beschreibung!

Ich schreibe sie vor, und erkläre sie. Also eigentlich wie immer ;)

public ItemStack makeItemStack(Material mat, int amount, String name, String lore) {	ItemStack item = new ItemStack(mat, amount);	ItemMeta itemMeta = item.getItemMeta();	itemMeta.setDisplayName(name);	itemMeta.setLore(Arrays.asList(lore));	item.setItemMeta(itemMeta);		return item;}

Und mit dem Erklären fange ich auch ganze vorne an: Mit "public". Es ist nicht ganz so schön, hier public genutzt zu haben, denn die Methode brauchen wir ja nur aus der Main-Klasse, die noch dazu die einzige Klasse im Projekt ist. Daher sollte sie eigentlich private sein. Aber damit du sie schön überall hineinkopieren kannst habe ich das mal so gemacht.

Bisher hast du vermutlich meistens mit "public void"-Methoden gearbeitet. Wie ich dir ganz zu Anfang schonmal gesagt habe bedeutet "void", dass die Methode nichts zurückgibt. "void" wird ersetzt durch den Variablentyp, wenn die Methode doch etwas zurückgeben soll.

In diesem Fall willst du die Methode ja nutzen, damit du damit ItemStacks generieren kannst, also willst du einen ItemStack zurückkriegen.

Daher hier "public ItemStack".

makeItemStack ist der Name der Methode.

Die Parameter.

Der erste Parameter ist auch hier wieder das Material und der Zweite die Anzahl. Anders als bei den eigentlichen Konstruktoren von ItemStacks kannst du aber als dritten Parameter den Namen des Items angeben! Der vierte Parameter ist die Beschreibung unter dem Item. Wenn ich das Thema "ArrayLists" erklärt habe, dann werde ich diese Methode noch updaten, sodass du mehrzeilige Beschreibungen machen kannst.

In der Methode drin generiere ich erstmal einen ItemStack, wie du es oben gewohnt bist, nur mit dem Material und der Anzahl.

Danach nehme ich die ItemMeta von diesem ItemStack. Die ItemMeta, das sind sozusagen die genaueren Daten des Items, zu denen zum Beispiel der Name und die Beschreibung gehören.

Mit dieser ItemMeta kann ich dann den "DisplayName", also den Anzeigenamen setzen.

Außerdem setze ich die "Lore" was die Beschreibung ist. Über das "Arrays.asList()" denkst du bitte gar nicht groß nach, bevor ich ArrayLists erklärt habe ;)

Da ich die ItemMeta modifiziert habe, muss ich sie dem Item neu zuweisen. Das mache ich hier mit der letzten Zeile.

"return item;" gibt dann nur noch das Item zurück.

Wie verwursten wir das jetzt in das Kommando? Lehn dich zurück, ich mach das schon:

if (cmd.getName().equalsIgnoreCase("item")) {	ItemStack item = makeItemStack(Material.BREAD, 21, "Lecker Semmel", "Noch backfrisch!");	Player p = (Player) sender;	p.getInventory().addItem(item);}

Es hat sich eigentlich nicht viel geändert, nur die Zeile, wo ich den ItemStack erstelle. Da ich die Parameter schon bei der Methode selbst erklärt habe denke ich, dass ihr das da oben doch schon alleine schafft ;)

Dann fehlt ja nur noch das Testen! :)

Wednesday_30_07_2014_22:22:12.png

Klappt!

Der gesamte Code sieht jetzt also so aus:

package de.minecraftforum.itemstacks;import java.util.Arrays;import org.bukkit.Material;import org.bukkit.command.Command;import org.bukkit.command.CommandSender;import org.bukkit.entity.Player;import org.bukkit.inventory.ItemStack;import org.bukkit.inventory.meta.ItemMeta;import org.bukkit.plugin.java.JavaPlugin;public class Main extends JavaPlugin {	public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {		if (!(sender instanceof Player)) {			sender.sendMessage("Das hast du doch nicht in der Konsole einzugeben! Also hör mal! :c");			return true;		}				if (cmd.getName().equalsIgnoreCase("item")) {			ItemStack item = makeItemStack(Material.BREAD, 21, "Lecker Semmel", "Noch backfrisch!");			Player p = (Player) sender;			p.getInventory().addItem(item);		}		return true;	}		public ItemStack makeItemStack(Material mat, int amount, String name, String lore) {		ItemStack item = new ItemStack(mat, amount);		ItemMeta itemMeta = item.getItemMeta();		itemMeta.setDisplayName(name);		itemMeta.setLore(Arrays.asList(lore));		item.setItemMeta(itemMeta);				return item;	}}

Ich hoffe, du hast die ItemStacks und ihre Funktionsweise jetzt verstanden, damit du sie in Zukunft fleißig nutzen kannst :)

Natürlich würde ich mich über ein "Gefällt mir" am unteren rechten Beitragsrand freuen :)

Liebe Grüße,

Baustein

928d6878c0.png

Es wird Zeit. Gerade in den letzten Teilen rede ich immer wieder von ArrayLists und HashMaps. Aber was sind ArrayLists und HashMaps?

Es wird also wieder ein Java-Teil und kein Bukkit-Teil.

Meine Main-Klasse ist jetzt also die Standard-Main-Klasse von Java:

package de.minecraftforum.java;public class Main {	public static void main(String[] args) {			}}

Fangen wir mit ArrayLists an. ArrayLists sind dafür da, Listen von Variablen eines bestimmten Typs zu erstellen. Das ließe sich beispielsweise gut nutzen, wenn man ein Vanish-Plugin programmiert und eine Liste von allen Unsichtbaren braucht.

Dann würde mein eine ArrayList des Typs Player machen.

Im Folgenden werde ich aber eine ArrayList machen, die ganze Zahlen speichert.

public static void main(String[] args) {	ArrayList<Integer> arrayList1 = new ArrayList<Integer>();}

Okay, das sieht komisch aus, nicht wahr? Warum gebe ich den Variablentyp in den <- und >-Zeichen an? Das ist aber wieder ein anderes Thema: Generics. Wichtig ist nur, dass du dir merkst, dass du sowohl bei ArrayLists als auch bei HashMaps, die ja gleich noch kommen, den / die Variablentyp/en in <> eingerahmt nach "ArrayList" oder "HashMap" angeben müsst.

Es ist nicht unbedingt nötig, den Variablentyp hinten noch einmal anzugeben. Die Zeile könnte also auch so aussehen:

ArrayList<Integer> arrayList1 = new ArrayList<>();

Hier passiert wieder etwas, was ich im letzten Teil erklärt habe. Wir erstellen ein Objekt von der Klasse "ArrayList". mit new ArrayList() wird der Konstruktor der Klasse aufgerufen.

Wenn du möchtest kannst du auch

List<Integer> arrayList1 = new ArrayList<>();

machen, nur hinten muss ArrayList stehen. Dabei solltest du aber auch darauf achten, dass du List von java.util und nicht von java.awt importierst.

So, jetzt gibt's also die Liste. Aber was lässt sich damit machen? Gehen wir mal durch ein paar Methoden.

public static void main(String[] args) {	ArrayList<Integer> arrayList1 = new ArrayList<>();		arrayList1.add(1);	int integer = 1337;	arrayList1.add(integer);		arrayList1.remove(0);	arrayList1.remove(integer);		arrayList1.set(0, integer);		if (arrayList1.contains(integer)) System.out.println(true);	else System.out.println(false);		arrayList1.clear();		arrayList1.add(integer);	arrayList1.add(integer + 2);	int entry = arrayList1.get(1);	System.out.println(entry);	int entry2 = arrayList1.get(integer);	System.out.println(entry2);		if (arrayList1.isEmpty()) System.out.println(true);	else System.out.println(false);		System.out.println(arrayList1.size());		arrayList1.clear();		if (arrayList1.isEmpty()) System.out.println(true);	else System.out.println(false);		System.out.println(arrayList1.size());}

Mit dem Erklären fange ich natürlich auch ganz oben an.

  • Mit der Methode add() kannst etwas zu der Liste hinzufügen. Hier ist das natürlich eine Integer.
  • Danach erstelle ich eine Variable des Typs Integer mit dem Wert 1337.
  • Diesen füge ich auch der Liste hinzu.
  • Nun entferne ich den Eintrag mit dem Index 0. Dazu muss man wissen, dass Java wann immer es geht bei 0 anfängt zu zählen. Der erste Eintrag in unserer "menschlichen" Sprache ist also der "nullte" Eintrag für Java. Es wird also die 1 entfernt.
  • Jetzt entferne ich die 1337 aus der Liste. Oder nicht? Nein! Wer den Code laufen gelassen hat wird gemerkt habe, dass eine Exception geworfen wird. Denn bei ArrayLists mit Integern arbeiten ist tricky! Java sieht die 1337 jetzt als Index-Angabe an, und wirft eine IndexOutOfBoundsException, weil die ArrayList gar keine 1338 Einträge hat!
  • Hier lege ich den nullten (also ersten ^^) Eintrag fest: Es soll 1337 sein.
  • In dieser Zeile geschieht die erste Ausgabe. Wenn die ArrayList 1337 enthält, so soll "true" ausgegeben werden, und wenn nicht, dann "false". Es wird also true ausgegeben.
  • Jetzt leere ich die ArrayList.
  • Ich füge der ArrayList die Werte 1337 und 1337 + 2 = 1339 hinzu.
  • Jetzt lege ich eine Integer "entry" fest, und weise ihr den Wert von der Zahl zu, die in der ArrayList an erster (also zweiter) Stelle steht.
  • Ich gebe entry aus: 1339!
  • Ich lege eine Integer entry2 fest, und weise ihr den Wert von der Zahl zu, die in der ArrayList an eintausenddreihuntersiebenunddreißigster (also eintausenddreihundertachtunddreißigster) Stelle steht. Hoppla! IndexOutOfBoundsException! Macht Sinn, die ArrayList ist ja gar nicht so groß. Die Zeile ist also Quatsch.
  • Da die vorige Zeile Quatsch war lass ich es lieber bleiben, Quatsch auszugeben. Die Zeile kann also auch weg.
  • Eine weitere Ausgabe: Wenn die ArrayList leer ist soll "true" ausgegeben werden. Ist sie es nicht, dann soll die Ausgabe "false" lauten. Und natürlich ist die Liste nicht leer, somit lautet die Ausgabe false.
  • Jetzt gebe ich die Größe der ArrayList aus. Das ist 2. Die Liste hat 2 Werte. Aber warum ist die Größe dann nicht 1, Java fängt doch bei 0 an zu zählen!? Ja, aber Java muss ja auch für eine leere Liste einen Größen-Wert haben. Dieser wäre natürlich 0. Bei size() zählt Java also genauso wie wir. Verwirrend, nicht? o.O
  • Ich leere die ArrayList wieder.
  • Ich gebe wieder "true" und "false" aus, je nachdem, ob die Liste leer ist oder nicht. Da ich sie ja eben erst geleert habe ist sie es natürlich. Die Ausgabe ist "true".
  • Ich gebe nochmal die Größe der ArrayList aus: Hier ist es die 0. Wie ich oben gesagt habe, eine leere ArrayList braucht ja auch eine Größe.

Das sind also ArrayLists. Schick! Statt mit Integers könntest du auch mit Strings oder sogar mit Playern arbeiten! Je nach Größe deines Projekts solltest du dir aber überlegen, ob du nicht lieber mit Strings statt mit Playern Listen erstellen willst, da Strings wesentlich leichter zu verarbeiten sind. Mach einfach p.getName() und füge den in die Liste ein.In den meisten Fällen dürfte das aber keine Ressourcenprobleme geben, du wirst dich ja hoffentlich nicht gleich an ein Monsterprojekt werfen.

Dann ab zu den HashMaps.

HashMaps ähneln den ArrayLists sehr. Der einzige Unterschied ist, dass sie nicht eine, sondern zwei Variablen pro Eintrag speichern. Dabei ist die eine immer der Schlüssel, mit der man zu der zweiten kommen kann. Klingt komplizierter als es ist. Aber das wird sich gleich lösen.

public static void main(String[] args) {	HashMap<String, Integer> hashMap1 = new HashMap<String, Integer>();}

Das sieht der ArrayList doch auch ähnlich. Hier müssen halt nur zwei Variablentypen statt einem übergeben werden. Gar nicht so schwer!

Optional kannst du deine Zeile auch so abkürzen:

public static void main(String[] args) {	Map<String, Integer> hashMap1 = new HashMap<String, Integer>();}

Und jetzt kommen wieder ein paar Anwendungsmöglichkeiten, damit HashMaps auch klar werden.

public static void main(String[] args) {	HashMap<String, Integer> hashMap1 = new HashMap<String, Integer>();		hashMap1.put("Knight", 0);	hashMap1.put("Cruzer", 2);	hashMap1.put("TuxCraft", 12);	hashMap1.put("Derya", 27);	hashMap1.put("René", 41);	hashMap1.put("Baustein", 1337);		int bausteinsWert = hashMap1.get("Baustein");		System.out.println(hashMap1.isEmpty());		hashMap1.remove("René");	System.out.println(hashMap1.containsKey("René"));	System.out.println(hashMap1.containsValue(0));		System.out.println(hashMap1.size());		System.out.println(hashMap1.values());		hashMap1.clear();}
  • Ich erstelle einige Einträge in der HashMap, hier mit der Methode put(). Als ersten Parameter muss ich den Schlüssel angeben, als zweiten den Wert. Als Variablentyp für den Schlüssel habe ich ja bei der Erstellung der HashMap "String" angegeben, daher muss das auch ein String sein, was ein Name natürlich ist. Der zweite Parameter ist dann eine Integer, die durch diese Methode fest mit dem Schlüssel zusammengeschweißt wird. Ein Wert hat einen Schlüssel und ein Schlüssel hat einen Wert.
  • Hier erstelle ich eine Integer mit dem Wert, den ich bekomme, wenn ich den Wert des Schlüssels "Baustein" nehme. bausteinsWert hat also den Wert 1337.
  • Hier gebe ich aus, ob die HashMap leer ist. Natürlich "false".
  • Ich entferne den Eintrag aus der HashMap mit dem Schlüssel "René".
  • Ich gebe aus, ob ein Eintrag mit dem Schlüssel "René" vorhanden ist. Da ich ihn ja eben gelöscht habe: "false".
  • Ich gebe aus, ob ein Eintrag mit dem Wert 0 vorhanden ist. "true", Knight ist ja da.
  • Ich gebe die Größe der HashMap aus: 5
  • Ich gebe alle Werte der HashMap aus:[27, 12, 0, 1337, 2]
  • Ich leere die HashMap.

Wofür ist das denn gut? Beispielsweise, wenn ihr mit Passwörtern arbeitet. Ihr könnt nicht einfach eine Liste an Passwörtern haben, und wenn ein User ein Passwort eingibt, was in der Liste steht, bekommt er Zugriff auf irgendwas. Er muss sein Passwort eingeben. Hier wäre also eine HashMap<Player, String> von Nöten. Als Schlüssel der Player, der zu dem String des Passworts führt. Ressourcenfreundlicher wäre es natürlich auch hier, den Namen des Spielers zu nehmen, also eine HashMap<String, String> zu machen. (Bei Passwörtern sollte man mitterweile eh lieber UUIDs nehmen, da sich die Namen ja ändern können)

Joa, viel mehr gibt's zu den beiden eigentlich gar nicht zu sagen. Ich wünsche euch viel Spaß mit den ArrayLists, und freue mich, wenn irgendwer auf "Gefällt mir" am unteren rechten Beitragsrand klickt :)

Liebe Grüße,

Baustein

Link zu diesem Kommentar
Auf anderen Seiten teilen

Erstelle ein Benutzerkonto oder melde dich an, um zu kommentieren

Du musst ein Benutzerkonto haben, um einen Kommentar verfassen zu können

Benutzerkonto erstellen

Neues Benutzerkonto für unsere Community erstellen. Es ist einfach!

Neues Benutzerkonto erstellen

Anmelden

Du hast bereits ein Benutzerkonto? Melde dich hier an.

Jetzt anmelden
×
×
  • Neu erstellen...
B
B