Also mir ist jetzt nicht klar, warum der Spieler nach einem Wollblock noch einen weiteren Block auswählen muss (ich vermute mal um Fenster oder Wand zu setzen), wenn bereits das Auswählen des Stein oder Glasblocks reichen würde. Aber ich gehe jetzt mal davon aus, das hat einen Grund.
Ich versteh das so, dass jeder Spieler unabhängig von den anderen Spielern agiert, daher nehme ich an, du speicherst alles was ich hier nenne
pro Spieler. Dazu musst du dir bei allen Events die Quelle (also den Spieler) holen und dann über eine Hashmap oder ähnliches Daten mit dem Spieler speichern, darauf gehe ich jetzt mal nicht ein. Bedenke nur, dass du die Spielerdaten auch aufräumen musst, wenn der Spieler den Server verlässt.
Was du vermutlich suchst nennt sich Statemachine (
https://de.wikipedia.org/wiki/Endlicher_Automat sehr Mathematisch). Die Idee ist, dass jede Instanz (also jeder Spieler) sich in einem bestimmten Zustand befindet. Beispielsweise "Muss Wolle schlagen", "muss Stein/Glass schlagen", "muss Befehl eingeben". Man kann auch den Vergangenen Zustand nehmen "hat Wolle geschlagen", "hat Stein geschlagen", aber das ist in der Regel nicht so handlich.
Im Code kannst du dir das ganz einfach speichern, du erzeugst dir eine Variable, die den Zustand speichert und baust eine Logik, die abhängig vom aktuellen Zustand eine Aktion ausführt und ggf. den Zustand ändert. In schrecklichem Pseudocode sieht das ungefähr so aus
enum State {INACTIVE, WOOL_EXPECTED, TYPE_BLOCK_EXPECTED}
// pro Spieler speicherst du
State currentState = ...;
// in dem Event, welches den geschlagenen Block registriert
function onBlockHit (event) {
if not event.isplayer return;
// spielerobjekt irgendwo her holen (hashmap, etc.)
playdata = foobar.getPlayerData(event.source);
// abhängig von zustand aktion ausführen
switch(playerdata.currentState){
case INACTIVE:
error(du musst erst /befehl ausfuehren) // sofern du eine fehlermeldung willst
return
case WOOL_EXPECTED:
if event.block == wool
// dein Code fuer glass
// naechsten zustand setzen
playerdata.currentState = State.TYPE_BLOCK_EXPECTED
return;
else
error(du musst zuerst wolle schlagen)
return;
case TYPE_BLOCK_EXPECTED:
if event.block == glass
// code fuer glass
// keine Ahnung was danach passieren soll, ich vermute nochmal wolle schlagen?
playerdata.currentState = WOOL_EXPECTED;
return;
else if event.block == stone
// code fuer stone
// keine Ahnung was danach passieren soll, ich vermute nochmal wolle schlagen?
playerdata.currentState = WOOL_EXPECTED;
return;
else
error(du musst entweder wolle oder glass schlagen)
return;
// default case fuer programmierfehler
case default:
log.error(invalid case detected, fix your shitty code)
}
}
// in command handler fuer deinen befehl(e) musst du dann selbst gucken was du machst, irgendwie sowas vermutlich
function onCommand(event)
// spielerobjekt irgendwo her holen (hashmap, etc.)
playdata = foobar.getPlayerData(event.source);
if event.command ==enable
playerdata.currentState = WOOL_EXPECTED;
if event.command == disable
playerdata.currentState = INACTIVE;
else
error(unknown command)
// und fuer den spielerzustand koenntest du noch sowas machen
class MyPlayerData {
State currentState...
int foo = 2;
int bar = 5;
Location myLoc =...;
}
// erzeuge player daten bei join
function onPlayerJoin(player)
MyPlayerData data = new MyPlayerData;
MyPlugin.getInstance().playermap.put(player, data);
// loesche player daten
function onPlayerQuit(player)
MyPlugin.getInstance().playermap.remove(player)
// und in der plugin klasse
Map<Player, MyPlayerData> playermap = new IdentityHashMap<>();
hierzu lesen: https://docs.oracle.com/javase/8/docs/api/java/util/IdentityHashMap.html
(Für den Grasblock am Ende brauchst du einfach einen weiteren Zustand, ich denke es ist klar, wie das System funktioniert.)
Ich hoff das Beispiel illustriert die Idee ein bisschen. Welche Zustände du genau brauchst und in welchen Zuständen du etwas tun musst, hängt stark davon ab, wie sich das Plugin verhalten soll.
Zu den Fragen, wie du das nun in Bukkit implementierst. Sofern du kein Event behandelst, dass nicht mehrfach pro Sekunde ausgeführt wird, brauchst du dir oft nicht all zu große Gedanken machen. Wichtig ist, dass du im negativen Fall, also wenn dein Plugin nichts zu tun hat, die Methode schnell beendest. Es macht zum Beispiel keinen Sinn den Block eines Events zu prüfen, wenn der Spieler aktuell gar kein Haus baut. Es macht auch keinen Sinn das Block Event zu löschen, nur weil gerade kein Spieler das Plugin nutzt, das sind Optimierungen, die einfach in der Masse von größerem Code untergehen und den Code schlecht lesbar machen. Ich würde auch grundsätzlich anstreben alle Listener beim Start deines Plugins zu registrieren, sofern es nicht einen WIRKLICH GUTEN Grund gibt, dass sie dynamisch registriert werden. Das sollte auch dein Problem mit der Plugininstanz größtenteils lösen. Es macht an der Stelle auch keinen unterschied ob du 5 oder 500 Spieler hast. Das ist erstmal grundsätzlich ein sehr guter Stil.
Entscheidend ist eigentlich immer nur welcher Code in einem Event ausgeführt wird, wenn es nichts tut. Aber auch hier solltest du sehr vorsichtig sein, wenn du etwas optimierst. Viele Optimierungen, die die Befehlsreihenfolge betreffen werden auch vom Compiler durchgeführt, beispielsweise folgender Code.
playerData = getPlayerDataFrom() // dauert vielleicht ein bisschen
switch(n)
case 1:
player.money = 20;
case 2:
player.money = 40;
case 3:
server.sendmessage(wuuzzaaa)
Case 3 braucht den Wert von playerData gar nicht. Es ist daher naheliegend den Code in Case 1 und Case 2 zu kopieren. Der Compiler erkennt solche Fälle aber in der Regel selbst und erzeugt selbst duplizierten Code für die zwei Ausführungszweige. Dabei bleibt der eigentliche Quellcode aber gut lesbar.
@BlackHole hat dir ja schon grundsätzlich erklärt, wie du die Plugininstanz im Code nutzen kannst. Was du dabei aber nur bedenken solltest ist, dass ein übermäßiger Zugriff auf ein Singleton in aller Regel ein Zeichen von Codesmell ist. (
https://de.wikipedia.org/wiki/Smell_(Programmierung) Objekte, die man sehr oft im Code verwendet sind zudem of Gottobjekte (
https://de.wikipedia.org/wiki/Gottobjekt). Nun kannst du da nicht immer etwas dagegen tun, da du keinen Einfluss darauf hast, wie die Bukkit API aufgebaut ist, aber dir sollte zumindest das Problem bewusst sein. Spätestens wenn du deine Plugininstanz 2-3 Stufen tief durchreichst, solltest du überlegen ob es nicht andere Möglichkeiten gibt.
So kannst du z.B. deine Pluginklasse zuerst die Objekte initialisieren lassen und sie dann erst nachträglich einander bekannt machen. Das sind aber Fragen von gutem Softwaredesign, da gibt es nur sehr wenig objektive Möglichkeiten eine Lösung zu bewerten.
Kann ich feststellen, ob ein Event tatsächlich unregistriert wurde? Muss ich das Event tatsächlich selbst unregistrieren, oder wird das, wenn das Objekt, das das Event registriert hat nicht mehr gebraucht wird, von selbst unregistriert?
Wenn du den Listener deregistrierst, wird er nicht mehr über Events benachrichtigt. Die Pluginarchitektur von Bukkit ist hier leider totaler Murks, wesshalb Bukkit so ziemlich gar nichts automatisch wegräumt (Listen und Tasks glaub allerdings schon). In einer idealen Welt, müsstest du in der onDisable Methode dafür sorgen, dass sich dein Plugin restlos aus dem Server ausklingt, alle Listener löscht, alle Timer, Tasks und Threads beendet und alle Daten speichert. Würde sich jedes Plugin an diese Regel halten, so würde der /reload Befehl auch das tun was er verspricht. Da aber so ziemlich kein einziges Plugin das versucht, brauchst du dich ums Aufräumen fast nicht zu kümmern. Du kannst davon ausgehen, dass der Server nach dem onDisable beendet wird. Von daher musst du lediglich deine Daten sichern.
Java nutzt dynamische Speicherverwaltung. Ein Objekt, "dass du nicht mehr benutzt" kann trotzdem weiter existieren. Man kann da zwar ein wenig Voodoo machen, aber das lassen wir an der Stelle besser mal. Wenn du das genauer verstehen willst, solltest du vielleicht C oder auch C++ lernen, dann wird dir klar, dass irgendwas die Objekte irgendwann löschen muss, denn wie du sicherlich auch gemerkt hast, gibt es in Java keine Möglichkeit irgendwelche Objekte zu löschen. In C/C++ bist du dafür verantwortlich, in Java gibt es ein automatisches System, das
irgendwann nach Objekten sucht, die nicht mehr genutzt werden. (
http://javapapers.com/java/how-java-garbage-collection-works/) Die genaue Implementierung kann von JVM zu JVM unterschiedlich ausfallen. Die Idee ist allerdings immer die, dass ein Objekt erst dann gelöscht wird, wenn es über keine Referenzen aus einem "Root" erreichbar ist. "Root" sind verschiedene Objekte und Strukturen. Beispielsweise Threads oder statische Attribute. Wenn du in deinem Plugin die Referenz auf einen Listener auf
null setzt, so ist noch immer eine Referenz im Event System von Bukkit gespeichert und wird daher nicht gelöscht. Daher werden Listener auch nicht automatisch gelöscht, das ist nämlich gar nicht möglich. Jedenfalls nicht so wie du dir das vorstellst.