Sign GUI - Use the sign interface to get user input

Discussion in 'Resources' started by nisovin, Sep 24, 2013.

Thread Status:
Not open for further replies.
  1. Offline

    nisovin

    I just discovered today that this is possible, so I decided to write a quick class that handles this. Firstly, this requires ProtocolLib.

    Here is the main class:

    Code:
    import java.lang.reflect.InvocationTargetException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.bukkit.Bukkit;
    import org.bukkit.Location;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.util.Vector;
    
    import com.comphenix.protocol.ProtocolLibrary;
    import com.comphenix.protocol.ProtocolManager;
    import com.comphenix.protocol.events.ConnectionSide;
    import com.comphenix.protocol.events.ListenerPriority;
    import com.comphenix.protocol.events.PacketAdapter;
    import com.comphenix.protocol.events.PacketContainer;
    import com.comphenix.protocol.events.PacketEvent;
    
    public class SignGUI {
    
        protected ProtocolManager protocolManager;
        protected PacketAdapter packetListener;
        protected Map<String, SignGUIListener> listeners;
        protected Map<String, Vector> signLocations;
        
        public SignGUI(Plugin plugin) {
            protocolManager = ProtocolLibrary.getProtocolManager();        
            packetListener = new PacketListener(plugin);
            protocolManager.addPacketListener(packetListener);
            listeners = new ConcurrentHashMap<String, SignGUIListener>();
            signLocations = new ConcurrentHashMap<String, Vector>();
        }
        
        public void open(Player player, SignGUIListener response) {
            open(player, (Location)null, response);
        }
        
        public void open(Player player, Location signLocation, SignGUIListener response) {
            int x = 0, y = 0, z = 0;
            if (signLocation != null) {
                x = signLocation.getBlockX();
                y = signLocation.getBlockY();
                z = signLocation.getBlockZ();
            }
            
            PacketContainer packet = protocolManager.createPacket(133);
            packet.getIntegers().write(0, 0).write(1, x).write(2, y).write(3, z);
            
            try {
                protocolManager.sendServerPacket(player, packet);
                signLocations.put(player.getName(), new Vector(x, y, z));
                listeners.put(player.getName(), response);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        
        public void open(Player player, String[] defaultText, SignGUIListener response) {
            List<PacketContainer> packets = new ArrayList<PacketContainer>();
            
            int x = 0, y = 0, z = 0;
            if (defaultText != null) {
                x = player.getLocation().getBlockX();
                z = player.getLocation().getBlockZ();
                
                PacketContainer packet53 = protocolManager.createPacket(53);
                packet53.getIntegers().write(0, x).write(1, y).write(2, z).write(3, 63).write(4, 0);
                packets.add(packet53);
                
                PacketContainer packet130 = protocolManager.createPacket(130);
                packet130.getIntegers().write(0, x).write(1, y).write(2, z);
                packet130.getStringArrays().write(0, defaultText);
                packets.add(packet130);
            }
            
            PacketContainer packet133 = protocolManager.createPacket(133);
            packet133.getIntegers().write(0, 0).write(1, x).write(2, y).write(3, z);
            packets.add(packet133);
            
            if (defaultText != null) {
                PacketContainer packet53 = protocolManager.createPacket(53);
                packet53.getIntegers().write(0, x).write(1, y).write(2, z).write(3, 7).write(4, 0);
                packets.add(packet53);
            }
            
            try {
                for (PacketContainer packet : packets) {
                    protocolManager.sendServerPacket(player, packet);
                }
                signLocations.put(player.getName(), new Vector(x, y, z));
                listeners.put(player.getName(), response);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }
        
        public void destroy() {
            protocolManager.removePacketListener(packetListener);
            listeners.clear();
            signLocations.clear();
        }
        
        public interface SignGUIListener {
            public void onSignDone(Player player, String[] lines);
        }
        
        class PacketListener extends PacketAdapter {
            
            Plugin plugin;
            
            public PacketListener(Plugin plugin) {
                super(plugin, ConnectionSide.CLIENT_SIDE, ListenerPriority.NORMAL, 0x82);
                this.plugin = plugin;
            }
            
            @Override
            public void onPacketReceiving(PacketEvent event) {
                final Player player = event.getPlayer();
                Vector v = signLocations.remove(player.getName());
                if (v == null) return;
                List<Integer> list = event.getPacket().getIntegers().getValues();
                if (list.get(0) != v.getBlockX()) return;
                if (list.get(1) != v.getBlockY()) return;
                if (list.get(2) != v.getBlockZ()) return;
                
                final String[] lines = event.getPacket().getStringArrays().getValues().get(0);
                final SignGUIListener response = listeners.remove(event.getPlayer().getName());
                if (response != null) {
                    event.setCancelled(true);
                    Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
                        public void run() {
                            response.onSignDone(player, lines);
                        }
                    });
                }
            }
            
        }
        
    }
    
    
    And here is how you use it:

    Code:
        SignGUI signGui;
     
        @Override
        public void onEnable() {
            signGui = new SignGUI(this);
        }
     
        public void someFunction() {
            signGui.open(player, new String[] { "test0", "test1", "test2", "test3" }, new SignGUI.SignGUIListener() {
                @Override
                public void onSignDone(Player player, String[] lines) {
                    // do something with the input
                    System.out.println(lines[0]);
                }
            });
        }
     
    
    You should only create one instance of the SignGUI object when using this. It is also probably a good idea to call destroy() in your onDisable() method, to remove the packet listener.
     
    Jumb_1907, FisheyLP, Zupsub and 16 others like this.
  2. Offline

    Skyost

  3. Offline

    bobacadodl

  4. Offline

    nisovin

    I've updated the code to fix some thread safety issues that Comphenix pointed out.
     
    bobacadodl and Comphenix like this.
  5. Offline

    Comphenix

    You should be safe now, at least from ConcurrentModificationExceptions. I was mainly concerned with the fact that the server could process multiple Packet130UpdateSign (0x82), each in their own dedicated thread, and update the signLocations map concurrently. In addition, users of this class probably don't expect onSignDone() is executed asynchronously, and might accessed parts of the Bukkit API wrongly (like bobacadodl used to do).

    ProtocolLib does clean up after plugins if they happen to forget their listeners (they might have crashed or the author forgot to do it), but it's never a bad idea to clean up after yourself.
     
    glen3b, Cirno, Garris0n and 2 others like this.
  6. Offline

    Flybelette

    Any 1.7 version ? :p
     
  7. Offline

    bobacadodl

    Just change the packet IDs, because they changed in 1.7.

    53->35
    130->51
    133-> 54

    You will also need a 1.7.2 version of protocolLib. (I dont know if that's been released yet)
     
  8. Offline

    Flybelette

    Thanks, Already made it, but give me an error, still seeing why :p

    Okay, it give me that :
    Code:
    Caused by: com.comphenix.protocol.reflect.FieldAccessException: Field index must be within 0 - count
    >      at com.comphenix.protocol.reflect.StructureModifier.write(StructureModifier.java:285)
    Caused by: java.lang.IndexOutOfBoundsException: Out of bounds
    >      ... 17 more
    Hum...

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: Nov 22, 2015
  9. Offline

    Quaro

    If you could make it undepended of other plugins, it would be awesome.
     
  10. Offline

    TichoCZ

    Are you planing to update this (if is it possible)? It doesnt work now (at least for me).
     
    Flybelette likes this.
  11. Offline

    bobacadodl

    Flybelette likes this.
  12. Offline

    Flybelette

  13. Offline

    Flybelette

    Sorry for the double post :/ Any update ? :p
     
  14. Offline

    Unfou

    Any way to make it work again ?
     
    Flybelette likes this.
  15. Offline

    stokdam

    same problem
     
  16. Offline

    Flybelette

    Hope that nisovin could update this, it's awesome :p
     
  17. Offline

    stokdam

    Try this
    Code:
    import java.lang.reflect.InvocationTargetException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    import org.bukkit.Bukkit;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.util.Vector;
    
    import com.comphenix.protocol.PacketType;
    import com.comphenix.protocol.ProtocolLibrary;
    import com.comphenix.protocol.ProtocolManager;
    import com.comphenix.protocol.events.PacketAdapter;
    import com.comphenix.protocol.events.PacketContainer;
    import com.comphenix.protocol.events.PacketEvent;
    
    
    public class SignGUI {
    
        protected ProtocolManager protocolManager;
        protected PacketAdapter packetListener;
        protected Map<String, SignGUIListener> listeners;
        protected Map<String, Vector> signLocations;
    
        
        
        public SignGUI(Plugin plugin) {
            protocolManager = ProtocolLibrary.getProtocolManager();        
            listeners = new ConcurrentHashMap<String, SignGUIListener>();
            signLocations = new ConcurrentHashMap<String, Vector>();
            
    
            ProtocolLibrary.getProtocolManager().addPacketListener(
            packetListener =  new PacketAdapter(plugin, PacketType.Play.Client.UPDATE_SIGN) 
            {
                @Override
                public void onPacketReceiving(PacketEvent event) {
                    final Player player = event.getPlayer();
                    
                    Vector v = signLocations.remove(player.getName());
                    if (v == null) return;        
                    List<Integer> list = event.getPacket().getIntegers().getValues();
                    if (list.get(0) != v.getBlockX()) return;
                    if (list.get(1) != v.getBlockY()) return;
                    if (list.get(2) != v.getBlockZ()) return;
                    
                    final String[] lines = event.getPacket().getStringArrays().getValues().get(0);
                    final SignGUIListener response = listeners.remove(event.getPlayer().getName());
                    if (response != null) {
                        event.setCancelled(true);
                        Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
                            public void run() {
                                response.onSignDone(player, lines);
                            }
                        });
                    }
                }
            });
        }
        
        
        public void open(Player player, String[] defaultText, SignGUIListener response) {
            List<PacketContainer> packets = new ArrayList<PacketContainer>();
            
            int x = 0, y = 0, z = 0;
            if (defaultText != null) {
                x = player.getLocation().getBlockX();
                z = player.getLocation().getBlockZ();
                
                PacketContainer packet53 = protocolManager.createPacket(PacketType.Play.Server.BLOCK_CHANGE);
                packet53.getIntegers().write(0, x).write(1, y).write(2, z);
                packet53.getBlocks().write(0, org.bukkit.Material.SIGN_POST);
                packets.add(packet53);
                
                PacketContainer packet130 = protocolManager.createPacket(PacketType.Play.Server.UPDATE_SIGN);
                packet130.getIntegers().write(0, x).write(1,y).write(2, z);
                packet130.getStringArrays().write(0, defaultText);
                packets.add(packet130);
            }
            
            PacketContainer packet133 = protocolManager.createPacket(PacketType.Play.Server.OPEN_SIGN_ENTITY);
            packet133.getIntegers().write(0, x).write(2, z);
            packets.add(packet133);
            
            if (defaultText != null) {
                PacketContainer packet53 = protocolManager.createPacket(PacketType.Play.Server.BLOCK_CHANGE);
                packet53.getIntegers().write(0, x).write(1, 0).write(2, z);
                packet53.getBlocks().write(0, org.bukkit.Material.BEDROCK);
                packets.add(packet53);
            }
            
            try {
                for (PacketContainer packet : packets) {
                    protocolManager.sendServerPacket(player, packet);
                }
                signLocations.put(player.getName(), new Vector(x, y, z));
                listeners.put(player.getName(), response);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }       
            
        }
        
        
        
    
        
        public void destroy() {
            protocolManager.removePacketListener(packetListener);
            listeners.clear();
            signLocations.clear();
        }
        
        public interface SignGUIListener {
            public void onSignDone(Player player, String[] lines);
           
        }
            
    }
    
     
  18. Offline

    Flybelette

    Thanks a lot, it worked ! :D
     
  19. Offline

    Blockworld2013

    How can I do it without ProtocolLib?
    I just need the part, where I get the Input.
     
  20. Offline

    MiniDigger

    Can somebody update this to the new protocol?
     
  21. Offline

    PurePlugins

    Anyone wanna update to newest protocol?
     
  22. Offline

    steffansk1997

    Is there a way to do this with the book gui?
     
  23. Offline

    FisheyLP

    Is there a way to do it without ProtocolLib?
     
  24. Offline

    RingOfStorms

    Of course there is, simply write your own packet listener by injecting custom PlayerConnection classes into each player as they join. You have to override every single one of the on packet receive methods and write some sort of either listener or handler for every packet that is sent from the client.
     
  25. Offline

    FisheyLP

    @RingOfStorms I tried a little bit with nms and reflections and came up with this solution:
    1. The sign class has the variable TileEntitySign sign in it.
    2. The class TileEntitySign has the variables HumanEntity k and boolean isEditable in it.
    3. I set the variable k to the ((CraftPlayer) p).getHandle() and set isEditable to true.
    4. Next I sent the PacketPlayOutOpenSignEditor to the player with the sign coordinates in the arguments.
    5. I added the player to an ArrayList.
    6. On SignChangeEvent, I checked if the player is in the
    7. I set the variables k to null and isEditable to false again.
    It is neccessary to have an existing sign, but thats no problem :D
     
  26. Offline

    jerome_coder

    Hello all,
    @nisovin your code doesn't work on 1.8
    Can anyone help me to get it work on 1.8?
    I've tried without protocolib but I have a warning in console and it says that I tried to edit a non editable sign.
    And with protocol lib nothing works. No error and no sign gui.
    Thanks!
     
  27. Offline

    MCMatters

  28. Offline

    Krumb069

    dont work with spigot 1.7.10 ?
     
  29. Offline

    laubsauger

    I rewrote it and it's working again [1.8]:
    Code:
    import java.lang.reflect.InvocationTargetException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import org.bukkit.Bukkit;
    import org.bukkit.Material;
    import org.bukkit.entity.Player;
    import org.bukkit.plugin.Plugin;
    import org.bukkit.util.Vector;
    import com.comphenix.protocol.PacketType;
    import com.comphenix.protocol.ProtocolLibrary;
    import com.comphenix.protocol.ProtocolManager;
    import com.comphenix.protocol.events.PacketAdapter;
    import com.comphenix.protocol.events.PacketContainer;
    import com.comphenix.protocol.events.PacketEvent;
    import com.comphenix.protocol.wrappers.BlockPosition;
    import com.comphenix.protocol.wrappers.WrappedBlockData;
    import com.comphenix.protocol.wrappers.WrappedChatComponent;
    public class SignGUI {
        protected ProtocolManager protocolManager;
        protected PacketAdapter packetListener;
        protected Map<String, SignGUIListener> listeners;
        protected Map<String, Vector> signLocations;
    
        public SignGUI(Plugin plugin) {
            protocolManager = ProtocolLibrary.getProtocolManager();   
            listeners = new ConcurrentHashMap<String, SignGUIListener>();
            signLocations = new ConcurrentHashMap<String, Vector>();
        
            ProtocolLibrary.getProtocolManager().addPacketListener(
                packetListener =  new PacketAdapter(plugin, PacketType.Play.Client.UPDATE_SIGN) {
                    @Override
                    public void onPacketReceiving(PacketEvent event) {
                        final Player player = event.getPlayer();
                        Vector v = signLocations.remove(player.getName());
                        BlockPosition bp = event.getPacket().getBlockPositionModifier().getValues().get(0);
                        final WrappedChatComponent[] chatarray = event.getPacket().getChatComponentArrays().getValues().get(0);
                        final String[] lines = {chatarray[0].getJson(), chatarray[1].getJson(), chatarray[2].getJson(), chatarray[3].getJson()};
                        final SignGUIListener response = listeners.remove(event.getPlayer().getName());
                     
                        if (v == null) return;  
                        if (bp.getX() != v.getBlockX()) return;
                        if (bp.getY() != v.getBlockY()) return;
                        if (bp.getZ() != v.getBlockZ()) return;
                        if (response != null) {
                            event.setCancelled(true);
                            Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
                                public void run() {
                                    response.onSignDone(player, lines);
                                }
                            });
                        }
                    }
                }
            );
        }
    
    
        public void open(Player player, String[] defaultText, SignGUIListener response) {
            List<PacketContainer> packets = new ArrayList<PacketContainer>();
            int x = player.getLocation().getBlockX();
            int y = 0;
            int z = player.getLocation().getBlockZ();
            BlockPosition bpos = new BlockPosition(x, y, z);
            PacketContainer packet133 = protocolManager.createPacket(PacketType.Play.Server.OPEN_SIGN_ENTITY);
         
            if (defaultText != null) {
                PacketContainer packet53 = protocolManager.createPacket(PacketType.Play.Server.BLOCK_CHANGE);
                PacketContainer packet130 = protocolManager.createPacket(PacketType.Play.Server.UPDATE_SIGN);
                WrappedBlockData iblock = WrappedBlockData.createData(Material.SIGN_POST);
                WrappedChatComponent[] cc = {WrappedChatComponent.fromText(defaultText[0]), WrappedChatComponent.fromText(defaultText[1]), WrappedChatComponent.fromText(defaultText[2]), WrappedChatComponent.fromText(defaultText[3])};
             
                packet53.getBlockPositionModifier().write(0, bpos);
                packet53.getBlockData().write(0, iblock);
                packet130.getBlockPositionModifier().write(0, bpos);
                packet130.getChatComponentArrays().write(0, cc);
                packets.add(packet53);
                packets.add(packet130);
            }
        
         
            packet133.getBlockPositionModifier().write(0, bpos);
            packets.add(packet133);
        
            if (defaultText != null) {
                PacketContainer packet53 = protocolManager.createPacket(PacketType.Play.Server.BLOCK_CHANGE);
                WrappedBlockData iblock = WrappedBlockData.createData(Material.BEDROCK);
             
                packet53.getBlockPositionModifier().write(0, bpos);
                packet53.getBlockData().write(0, iblock);
                packets.add(packet53);
            }
        
            try {
                for (PacketContainer packet : packets) {
                    protocolManager.sendServerPacket(player, packet);
                }
             
                signLocations.put(player.getName(), new Vector(x, y, z));
                listeners.put(player.getName(), response);
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } 
        
        }
    
        public void destroy() {
            protocolManager.removePacketListener(packetListener);
            listeners.clear();
            signLocations.clear();
        }
    
        public interface SignGUIListener {
            public void onSignDone(Player player, String[] lines);
        }
    }
    Usage:
    Code:
    public static void openSign(Player p) {     
            Main.getSignGUI().open(p, new String[] { "test0", "test1", "test2", "test3" }, new SignGUI.SignGUIListener() {
                @Override
                public void onSignDone(Player player, String[] lines) {
                    System.out.println(lines[0]);
                }
            });
        }
    I hope this helps you :)
     
    Last edited: Nov 22, 2015
    Jumb_1907 likes this.
Thread Status:
Not open for further replies.

Share This Page