[Util] SignInput - Eingaben von Schildern abfangen

Hier könnt ihr anderen Leuten helfen, indem ihr Anleitungen oder praktische Codesegmente zur Verfügung stellt.

[Util] SignInput - Eingaben von Schildern abfangen

Beitragvon Sep2703 » Mi 8. Apr 2015, 15:47

Hallo,

ich möchte heute meine SignInputAPI-Klasse vorstellen.
Die Klasse ermöglicht es, Spielern einen virtuellen Schild-Editor zu öffnen und die Eingabe des Benutzers abzufangen.
Beispielhafte Einsatzgebiete sind Wirtschaftssysteme (Konto-Buchungen) oder auch Konfigurationsmodi.

Klasse

Code: Alles auswählen
  1. package de.janhektor.endergamez.util;
  2. import io.netty.channel.Channel;
  3. import io.netty.channel.ChannelHandlerContext;
  4. import io.netty.handler.codec.MessageToMessageDecoder;
  5. import java.lang.reflect.Array;
  6. import java.lang.reflect.Constructor;
  7. import java.lang.reflect.Field;
  8. import java.lang.reflect.Method;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. import java.util.Map;
  12. import java.util.UUID;
  13. import java.util.function.Consumer;
  14. import org.bukkit.Bukkit;
  15. import org.bukkit.entity.Player;
  16. import org.bukkit.event.EventHandler;
  17. import org.bukkit.event.Listener;
  18. import org.bukkit.event.player.PlayerJoinEvent;
  19. import org.bukkit.plugin.Plugin;
  20. public final class SignInputAPI implements Listener, Runnable {
  21.    /**
  22.     * @author Janhektor
  23.     * This class in licensed under GPLv3
  24.     * For more information look at http://www.gnu.org/licenses/gpl-3.0
  25.     */
  26.    
  27.    private final static String VERSION;
  28.    
  29.    static {
  30.       String path = Bukkit.getServer().getClass().getPackage().getName();
  31.       VERSION = path.substring(path.lastIndexOf(".") + 1, path.length());
  32.    }
  33.    
  34.    
  35.    private final Plugin plugin;
  36.    private final Map<UUID, Consumer<String[]>> inputResults;
  37.    public SignInputAPI(Plugin plugin) {
  38.       this.plugin = plugin;
  39.       this.inputResults = new HashMap<UUID, Consumer<String[]>>();
  40.       Bukkit.getScheduler().runTaskTimer(this.plugin, this, 0L, 20 * 3L);
  41.       
  42.    }
  43.    
  44.    /**
  45.     * Use this method to read the SignInput from a player
  46.     * The accept()-method of your consumer will be called, when the player close the sign
  47.     * @return boolean successful
  48.     * @param p - The Player, who have to type an input
  49.     * @param result - The consumer (String[]) for the result; String[] contains strings for 4 lines
  50.     */
  51.    public boolean readInput(Player p, Consumer<String[]> result) {
  52.       inputResults.put(p.getUniqueId(), result);
  53.       try {
  54.          Class<?> packetClass = Class.forName(getNMSClass("PacketPlayOutOpenSignEditor"));
  55.          Class<?> blockPositionClass = Class.forName(getNMSClass("BlockPosition"));
  56.          Constructor<?> blockPosCon = blockPositionClass.getConstructor(new Class[] { int.class, int.class, int.class });
  57.          Object blockPosition = blockPosCon.newInstance(new Object[] { 0, 0, 0 });
  58.          Constructor<?> packetCon = packetClass.getConstructor(new Class[] { blockPositionClass });
  59.          Object packet = packetCon.newInstance(new Object[] { blockPosition });
  60.          
  61.          Method getHandle = p.getClass().getMethod("getHandle");
  62.          Object nmsPlayer = getHandle.invoke(p);
  63.          Field pConnectionField = nmsPlayer.getClass().getField("playerConnection");
  64.          Object pConnection = pConnectionField.get(nmsPlayer);
  65.          Method sendMethod = pConnection.getClass().getMethod("sendPacket", new Class[] { Class.forName(getNMSClass("Packet")) });
  66.          sendMethod.invoke(pConnection, new Object[] { packet });
  67.          return true;
  68.       } catch (Exception ex) {
  69.          ex.printStackTrace();
  70.          return false;
  71.       }
  72.    }
  73.    /* Garbage Collection */
  74.    @Override
  75.    public void run() {
  76.       for (UUID uuid : inputResults.keySet()) {
  77.          if (Bukkit.getPlayer(uuid) == null) inputResults.remove(uuid);
  78.       }
  79.    }
  80.    
  81.    /* Events */
  82.    @EventHandler
  83.    public void onJoin (PlayerJoinEvent e) {
  84.       Player p = e.getPlayer();
  85.       getNettyChannel(p).pipeline().addAfter("decoder", "signListener", new MessageToMessageDecoder<Object>() {
  86.          @Override
  87.          protected void decode(ChannelHandlerContext chc, Object packet, List<Object> packetList) throws Exception {
  88.             if (instanceOf(packet, getNMSClass("PacketPlayInUpdateSign"))) {
  89.                Method bMethod = packet.getClass().getMethod("b");
  90.                Object chatBaseComponents = bMethod.invoke(packet);
  91.                String[] lines = new String[4];
  92.                for (int i = 0; i < 4; i++) {
  93.                   Object chatComponent = Array.get(chatBaseComponents, i);
  94.                   Method getText = chatComponent.getClass().getMethod("getText");
  95.                   lines[i] = (String) getText.invoke(chatComponent);
  96.                }
  97.                if (inputResults.containsKey(p.getUniqueId())) {
  98.                   inputResults.get(p.getUniqueId()).accept(lines);
  99.                   inputResults.remove(p.getUniqueId());
  100.                }
  101.             }
  102.             packetList.add(packet);
  103.          }
  104.       });
  105.    }
  106.    
  107.    /* Util Methods */
  108.    private Channel getNettyChannel(Player p) {
  109.       Channel ch = null;
  110.       try {
  111.          Method getHandle = p.getClass().getMethod("getHandle");
  112.          Object nmsPlayer = getHandle.invoke(p);
  113.          Field pConnectionField = nmsPlayer.getClass().getField("playerConnection");
  114.          Object pConnection = pConnectionField.get(nmsPlayer);
  115.          Field networkManagerField = pConnection.getClass().getField("networkManager");
  116.          Object networkManager = networkManagerField.get(pConnection);
  117.          ch = (Channel) networkManager.getClass().getField("k").get(networkManager);
  118.       } catch (Exception ex) {
  119.          ex.printStackTrace();
  120.       }
  121.       return ch;
  122.    }
  123.    
  124.    private boolean instanceOf(Object o, String className) {
  125.       try {
  126.          return Class.forName(className).isInstance(o);
  127.       } catch (ClassNotFoundException e) {
  128.          e.printStackTrace();
  129.       }
  130.       return false;
  131.    }
  132.    
  133.    private String getNMSClass(String className) {
  134.       return "net.minecraft.server." + VERSION + "." + className;
  135.    }
  136. }

Lizenz: GPLv3 (Anpassungen sind somit erlaubt)


Verwendung
Die Klasse ist einfach zu verwenden. Ich empfehle, eine Instanz anzulegen und diese immer wieder zu benutzen.
In der Hauptklasse eines Plugins könnte das so ausschauen:
Code: Alles auswählen
  1.    private SignInputAPI signInput;
  2.    
  3.    @Override
  4.    public void onEnable() {
  5.       this.signInput = new SignInputAPI(this);
  6.    }
  7.    
  8.    public SignInputAPI getSignInput() {
  9.       return signInput;
  10.    }


Diese Vorarbeit muss geleistet werden, damit die Klasse verwendet werden kann.
Die Eingabe eines Spielers lässt sich dann wie folgt abfangen (Beispiel):
Code: Alles auswählen
  1.       Player p = (Player) sender;
  2.       plugin.getSignInput().readInput(p, new Consumer<String[]>() {
  3.          
  4.          @Override
  5.          public void accept(String[] lines) {
  6.             p.sendMessage("In der ersten Zeile steht: " + lines[0]);
  7.             p.sendMessage("In der vierten Zeile steht: " + lines[3]);
  8.          }
  9.       });


Hinweis: Die accept()-Methode vom Consumer wird dann aufgerufen, wenn der Spieler entweder ESC oder auf den Button "Done" drückt.


Voraussetzungen
Ich empfehle die Verwendung von Spigot 1.8 oder neuer.
Theoretisch sollte die Klasse auch mit einer älteren Version kompatibel sein. Falls dies nicht der Fall ist, bitte ich um einen Hinweis, sodass ich entsprechende Änderungen an der Klasse vornehmen kann.
Um die Klasse benutzen zu können, sollte Java 8 installiert sein. Alternativ könnt ihr folgendes Interface als Ersatz für den Java-Consumer verwenden:
Code: Alles auswählen
  1. public interface Consumer<T> {
  2.    void accept(T result);
  3. }

Dazu einfach ein neues Interface anlegen und den Code kopieren.


So, nun wünsche ich euch viel Spaß beim Nutzen dieser Klasse ;)
Janhektor / Sep2703
Du möchtest programmieren lernen oder dein Bukkit-/Spigot-Wissen erweitern?
Hier habe ich für dich kostenlose Tutorials: https://youtube.com/janhektor
Benutzeravatar
Sep2703
 
Beiträge: 677
Registriert: Mi 8. Jan 2014, 15:13
Wohnort: 127.0.0.1

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon Aquaatic » Mi 8. Apr 2015, 16:21

Interessante Klasse. Du fängst also die Packets ab? Du kannst ja vielleicht mal ein [EXPERT] Tutorial dazu auf deinem Kanal machen, würde ich feiern.
Mit freundlichen Grüßen
~ Aquaatic
Benutzeravatar
Aquaatic
 
Beiträge: 148
Registriert: Mo 16. Feb 2015, 12:51

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon Sep2703 » Mi 8. Apr 2015, 16:22

Aquaatic hat geschrieben:Interessante Klasse. Du fängst also die Packets ab? Du kannst ja vielleicht mal ein [EXPERT] Tutorial dazu auf deinem Kanal machen, würde ich feiern.


Ja, so ein Tutorial ist geplant. Ich würde auch direkt zeigen, wie man ausgehende Packets abfängt (Custom PlayerConnection) ;)
Du möchtest programmieren lernen oder dein Bukkit-/Spigot-Wissen erweitern?
Hier habe ich für dich kostenlose Tutorials: https://youtube.com/janhektor
Benutzeravatar
Sep2703
 
Beiträge: 677
Registriert: Mi 8. Jan 2014, 15:13
Wohnort: 127.0.0.1

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon Aquaatic » Mi 8. Apr 2015, 17:12

Sep2703 hat geschrieben:
Aquaatic hat geschrieben:Interessante Klasse. Du fängst also die Packets ab? Du kannst ja vielleicht mal ein [EXPERT] Tutorial dazu auf deinem Kanal machen, würde ich feiern.


Ja, so ein Tutorial ist geplant. Ich würde auch direkt zeigen, wie man ausgehende Packets abfängt (Custom PlayerConnection) ;)


Fände ich sehr, sehr geil, das ist was, mit was ich noch nie etwas am Hut hatte und man auf Google auch nix findet.
Mit freundlichen Grüßen
~ Aquaatic
Benutzeravatar
Aquaatic
 
Beiträge: 148
Registriert: Mo 16. Feb 2015, 12:51

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon ImGameboy » Mi 8. Apr 2015, 17:13

Vielen Dank für diese Klasse!
Ich hatte mal sowas ähnliches vor, hätte es aber wohl nicht so gut hinbekommen.
Werde sie sicherlich mal benötigen!
Lückenstopfer ^^
Benutzeravatar
ImGameboy
 
Beiträge: 210
Registriert: Mi 17. Sep 2014, 15:25

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon Admiral_Zott » Do 9. Apr 2015, 11:55

Schöne Klasse, werde ich auf jeden Fall benutzen :D
Benutzeravatar
Admiral_Zott
 
Beiträge: 220
Registriert: Do 10. Apr 2014, 11:56
Wohnort: Zu Hause

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon PiGGiE_ » Sa 16. Mai 2015, 22:13

Ich weiss nicht warum es bei mir nicht geht. Die accept() - Methode (also wenn jmd. Done oder ESC drückt) reagiert auf nichts. Ich verwende die Methode bei einem Kommando, habe es aber auch schon bei einem JoinEvent probiert. Spigot 1.8.3.
Benutzeravatar
PiGGiE_
 
Beiträge: 34
Registriert: Di 30. Dez 2014, 20:30

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon Admiral_Zott » Di 19. Mai 2015, 15:29

Kommt bei dir irgendeine Exseption? Wenn nicht, sende uns mal den Source von der Stelle, wo du die API benutzt.
Benutzeravatar
Admiral_Zott
 
Beiträge: 220
Registriert: Do 10. Apr 2014, 11:56
Wohnort: Zu Hause

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon Aquaatic » Di 19. Mai 2015, 16:06

PiGGiE_ hat geschrieben:Ich weiss nicht warum es bei mir nicht geht. Die accept() - Methode (also wenn jmd. Done oder ESC drückt) reagiert auf nichts. Ich verwende die Methode bei einem Kommando, habe es aber auch schon bei einem JoinEvent probiert. Spigot 1.8.3.

Soweit ich weiß ist das Field des Channels nicht mehr "k", sondern... lass mich kurz nachschauen... "i" (http://prntscr.com/76zuvu) Ich verwende dazu immer eine For-Schleife, die durch alle Felder iteriert und ermittelt, welches Feld "ein Channel ist".
Mit freundlichen Grüßen
~ Aquaatic
Benutzeravatar
Aquaatic
 
Beiträge: 148
Registriert: Mo 16. Feb 2015, 12:51

Re: [Util] SignInput - Eingaben von Schildern abfangen

Beitragvon PiGGiE_ » So 24. Mai 2015, 22:19

Leider funktioniert das Ganze auch nach dem Umbenennen des Channels nicht. :/

Ein Update auf die 1.8.3 wäre ganz nett. :)
Benutzeravatar
PiGGiE_
 
Beiträge: 34
Registriert: Di 30. Dez 2014, 20:30

Nächste

Zurück zu Anleitungen

Wer ist online?

Mitglieder in diesem Forum: Majestic-12 [Bot] und 2 Gäste

cron