Beiträge von Aquaatic

    Eleganter, nachdem UUID noch zum PK gemacht wurde:

    • add: INSERT INTO levelSystem (UUID, xp) VALUE (?, ?) ON DUPLICATE KEY UPDATE xp = xp + ?;
    • set: INSERT INTO levelSystem (UUID, xp) VALUE (?, ?) ON DUPLICATE KEY UPDATE xp = ?

    Das wird aber nicht funktionieren! Dein Plugin wird von Spigot gestartet, aber braucht das zu entpackende Plugin als Vorbedingung, die aber zu dem Zeitpunkt ja gar nicht erfüllt sein kann, weil es ja erst noch entpackt werden muss.

    Einfach das Plugin vorher mit in den Plugins-Ordner tun und gut ist - gerade wenn es privat genutzt wird, ist das ja völlig unproblematisch...


    Sollte deine API ohne Listener, Commands und ähnliches auskommen, kannst du sie auch als "Nicht-Plugin-Bibliothek" schreiben und mit in dein Plugin shaden (-> Maven), dann kannst du das aber immer nur genau ein mal pro Server verwenden, sonst verwirrst du den ClassLoader.

    Ja, okay, dann muss man sich überlegen, welche Daten nur für einen Server zu einer Zeit relevant sind / nicht verändert werden, während der Spieler auf dem Server ist. Den Rest müsste man dann immer abfragen. Falls die Performance wirklich so kritisch ist, kann man ja auch noch einen schnellen Cache (z.B. Redis) dazwischenschalten.

    Und dann noch vielleicht eine Warnung: bevor ein Multiproxysystem wirklich praktikabel ist, braucht man mehrere tausend Spieler gleichzeitig auf dem Server, davor kann man das ganze noch wunderbar auf einem entsprechend dimensionierten Bungee laufen lassen. Also lieber klein anfangen und wenn es dann mal darauf zugeht wirklich einen ganzen Server mit einem Bungee ausreizen, DANN kann man sich darüber Gedanken machen. Davor ist das nur unnötiger Aufwand und zusätzliche Fehleranfälligkeit für keinen Vorteil.

    Zu Frage 2: Wichtig ist vielleicht die Überlegung, dass ein Spieler sich zu einer Zeit immer nur auf einem Server befindet (und dem Proxy, aber da werden höchstwahrscheinlich keine konkurrierenden Situationen auftreten, oder?). Das heißt, dass man beim Joinen des Spielers einmal die Daten laden und beim Verlassen die Daten in die Datenbank schreiben kann. Man muss dann nur sicherstellen, dass die Daten geschrieben wurden, bevor der Spieler auf den nächsten Server geschoben wird.

    Ein weiterer Tipp: deine Datenbank kann auch rechnen! Anstatt erst die Stats zu erfragen (ich denke das macht getStatsReset), kannst du auch einfach deine Query in UPDATE SpeedCW SET STATSRESET = STATSRESET - ? WHERE UUID = ? abändern und in den ersten Parameter die abzuziehenden Punkte und in den zweiten die UUID setzen

    Die Art der Speicherung ist OK, u.U auch eine der performantesten... wie viele Zeilen hat die Tabelle denn? Sind Indizes gesetzt (hier insbesondere auf UUID)? Mal versucht zu "benchmarken", wie lange die Abfrage braucht? Zum Beispiel so:

    Code
    long start = System.currentTimeMillis();
    // DB-Abfrage
    System.out.println("DB-Abfrage hat " + (System.currentTimeMillis() - start) + "ms gedauert");

    Ich habe mir mal eben (tatsächlich seit Jahren mal wieder) einen kleinen Testserver eingerichtet und das ganz mal ausprobiert. Meine Befürchtung war:


    Spieler betritt den Server -> PostLoginEvent wird aufgerufen -> es wird festgestellt, dass Server voll -> p.disconnect(...) wird aufgerufen -> DisconnectEvent wird aufgerufen ohne, dass Spieler vorher in die playerIps Map eingetragen wurde -> nichts passiert -> LoginEvent wird fortgesetzt -> Spieler wird in playerIps Map eingetragen und nie wieder entfernt, weil sein DisconnectEvent schon aufgerufen wurde. Dadurch kann der Speicher für den Spieler nie freigegeben werden und man hat so einen Memory-Leak erzeugt.


    Das habe ich mal "simuliert", indem ich einfach mal

    • einen Listener fürs PostLoginEvent, der den Spieler disconnected, dann 5 Sekunden blockiert und schließlich eine Nachicht ausgibt und
    • einen Listener fürs DisconnectEvent, der einfach nur eine Nachicht ausgibt,

    geschrieben habe. Wenn die Nachicht im DisconnectEvent also vor der im PostLoginEvent geschrieben worden wäre, hätte sich meine Befürchtung bestätigt. Hat sie aber nicht. Das DisconnectEvent wurde erst nach vollständiger Ausführung des PostLoginEvents aufgerufen, hier ist man also (zumindest ist das jetzt experimentell bestätigt) auf der sicheren Seite.


    Mit dieser Sicherheit kann man den Code nochmal optimieren und sich die zweite Map doch sparen:

    Im PostLoginEvent wird der Zähler für den Spieler immer - egal, ob damit eine Grenze überschritten wird oder nicht - erhöht und im DisconnectEvent immer verringert.

    Wenn die Grenze im PostLoginEvent überschritten wird, wird der Spieler aber natürlich trotzdem disconnected. Zwar ist der Zähler dann nämlich erhöht, aber dadurch wird ja das DisconnectEvent aufgerufen und der Zähler wieder verringert, man befindet sich also wieder im Ausgangszustand, als hätte sich der Spieler nie verbunden. Das gilt dann genauso für alle anderen Disconnects im PostLoginEvent.

    In Code also:

    Noch eine Idee: was spricht denn dagegen, einfach ein neues Event einzuführen, das immer dann gefeuert wird, wenn das JoinEvent gefeuert wurde und die die Einstellungen übermittelt wurden? Davor ggf. noch wieder in den Main-Thread mit BukkitScheduler#runTask(...) eintreten, dann kann muss man sich auch um Parallelität keine Sorgen machen.

    In dem Event kann man dann alles ganz normal machen wie du es im JoinEvent machen willst. Das wäre so das idiomatischste, was mir einfällt.

    Okay, ich habe wieder den selben Fehler wie beim letzten mal eingebaut... n++ nochmal durch ++n ersetzen mit selbiger Begründung wie vorhin...


    Und dann noch vielleicht eine Idee: ich weiß nicht, wie intern die Disconnects gehandelt werden - ob der Spieler sofort disconnected wird, also sozusagen direkt nach p.disconnect(...) auch der Disconnect-Listener aufgerufen wird, oder aber ob das Event erst abgehandelt wird und dann der Spieler tatsächlich disconnected wird oder oder oder... ersteres wäre nämlich schlecht, dann würde die Spieler-Referenz nämlich für immer in der Map verweilen und es würde ab diesem Zeitpunkt immer ein Spieler auf dieser IP zu viel gespeichert sein -> Memory-Leak. Erste kurze Recherchen sagen mir, dass 250ms gewartet werden (aber nicht etwa, um solche Bugs zu vermeiden, sondern wegen irgendwelchen Minecraft-Protokoll Interna), das sollte reichen... an der Stelle bin ich aber auch vom Bungee API Design nicht so wirklich begeistert, weil das doch schon durchaus zu Fehlern (insbesondere auch eben zu Memory-Leaks) führen kann, wenn man Spieler-bezogene Daten im PostLoginEvent speichert.


    Falls dich das alles nicht so wirklich interessiert und du zufrieden bist, wenn dein Plugin funktioniert, ignoriere den letzten Absatz und verbessere einfach den oben genannten Fehler nochmal.

    Ouhh.. Okay.. Wenn man den Spieler im PostLoginEvent disconnected, dann zählt das auch als Quit vom Proxy.. Das heißt, wenn man die maximale Anzahl an Clients erreicht hat und dann joint, dann wird "n" beim Kicken vom Proxy um eins verringert..

    Ja, das macht Sinn. Vielleicht also noch eine Map<ProxiedPlayer, String> mit Player -> IP einbauen? Dann kannst du folgendermaßen vorgehen:

    Dann solltest du auf jeden Fall auf der sicheren Seite sein.

    Ja, entschuldige, da habe ich einen Fehler eingebaut. n++ muss zu ++n geändert werden (oder alternativ auch einfach (n + 1)), sonst wird immer 1 in die Map gespeichert, da n erst erhöht wird, nachdem es in die Map geschrieben wurde ("Post-Increment"), man möchte das aber ja davor machen ("Pre-Increment")

    Zunächst sind in der Map keine Einträge vorhanden, also gibt ips.get(...) null zurück. Um die Vergleichsoperation durchzuführen, muss aber das zurückgegebene Objekt unboxed werden (= Aufruf von Integer#intValue()). Wenn der Integer aber null ist, wird dadurch natürlich eine NPE geworfen. Du solltest also eher etwa folgendermaßen vorgehen:

    Code
    Integer n = ips.get(playerIp);
    if (n == null || n < maxPlayersPerIp) {
      ips.put(playerIp, n == null ? 1 : n++);
      // Kann joinen
    } else {
      // Halt stopp!
    }

    Der Schaden-Verursacher ist kein Creeper <=/=> Der Schaden-Nehmer ist ein Creeper


    Ersteres steht in der if-Bedingung der handleDamage-Methode, zweiteres in dem darrauf folgenden Cast. Du wolltest bestimmt stattdessen if (event.getEntity() instanceof Creeper) schreiben.