Recently I'm coding a gui and there is a feature that can let user enter keyword and do searching like this User click on this sign A fake (packet) sign gui will pop-up and the user will be able to enter keyword they want to search So my problem is, what is the best way for me to make this gui shows up again after user pressing "Done" or ESC to exit the sign gui? Currently I'm using an extra GUI constructor, accept the original GUI object and the result of the sign gui (If you are interested in code, there it is) Code (Text): // Extra constructor public MenuGUI(MenuGUI gui, String input) { this(gui.sorting, input, 1); Bukkit.broadcastMessage(input); } // Original constructor public MenuGUI(MenuSorting sorting, String searching, int page) { super(54, "Rooms"); this.rooms = new ArrayList<>(); this.sorting = sorting; if (sorting == null) this.sorting = MenuSorting.NONE; this.searching = searching; this.page = page; ......... And calling the gui back like this Code (Text): public SignGUI(Player player, MythicGUI mythicGui, String... display) { this.player = player; this.mythicGui = mythicGui; this.display = display; this.result = ""; } public void registerListener() { MythicDungeons.protocolManager.addPacketListener( listener = new PacketAdapter(plugin, PacketType.Play.Client.UPDATE_SIGN) { @Override public void onPacketReceiving(PacketEvent event) { ... ... ... Bukkit.getScheduler().runTask(plugin, new Runnable() { @Override public void run() { if (mythicGui instanceof CreateGUI) { CreateGUI createGui = new CreateGUI((CreateGUI) mythicGui, result); createGui.open(player); } if (mythicGui instanceof MenuGUI) { MenuGUI menuGui = new MenuGUI((MenuGUI) mythicGui, result); menuGui.open(player); } } }); } }); } But it seems like not very efficient and it doesn't seems really make sense. (I know I shouldn't compare custom gui like this, but I'm skipping that part bcz i can't even make the whole thing work... so, yea) So what should I do after user completes their input? (If you are really interested in the code) Spoiler Code (Text): public class SignGUI { MythicDungeons plugin = MythicDungeons.inst(); Player player; String[] display; Location loc; Block block; PacketListener listener; MythicGUI mythicGui; String result; public SignGUI(Player player, MythicGUI mythicGui, String... display) { this.player = player; this.mythicGui = mythicGui; this.display = display; this.result = ""; } public void registerListener() { MythicDungeons.protocolManager.addPacketListener( listener = new PacketAdapter(plugin, PacketType.Play.Client.UPDATE_SIGN) { @Override public void onPacketReceiving(PacketEvent event) { PacketContainer packet = event.getPacket(); Player player = event.getPlayer(); String[] input = packet.getStringArrays().read(0); player.sendBlockChange(loc, block.getBlockData()); unregisterListener(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < input.length; i++) { builder.append(input[i]); if (i < input.length - 1) builder.append(" "); } result = builder.toString(); Bukkit.getScheduler().runTask(plugin, new Runnable() { @Override public void run() { if (mythicGui instanceof CreateGUI) { CreateGUI createGui = new CreateGUI((CreateGUI) mythicGui, result); createGui.open(player); } if (mythicGui instanceof MenuGUI) { MenuGUI menuGui = new MenuGUI((MenuGUI) mythicGui, result); menuGui.open(player); } } }); } }); } public void unregisterListener() { MythicDungeons.protocolManager.removePacketListener(listener); } public void open() { registerListener(); loc = player.getLocation(); loc.setY(0); block = loc.getBlock(); player.sendBlockChange(loc, Material.SIGN.createBlockData()); PacketContainer openSignEditor = MythicDungeons.protocolManager.createPacket( PacketType.Play.Server.OPEN_SIGN_EDITOR); PacketContainer tileEntityData = MythicDungeons.protocolManager.createPacket( PacketType.Play.Server.TILE_ENTITY_DATA); BlockPosition blockPosition = new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); openSignEditor.getBlockPositionModifier().write(0, blockPosition); NbtCompound signNbt = NbtFactory.ofCompound("MythicDungeons"); signNbt.put("Text1", "{\"text\":\"" + display[0] + "\"}"); signNbt.put("Text2", "{\"text\":\"" + display[1] + "\"}"); signNbt.put("Text3", "{\"text\":\"" + display[2] + "\"}"); signNbt.put("Text4", "{\"text\":\"" + display[3] + "\"}"); signNbt.put("id", "minecraft:sign"); signNbt.put("x", loc.getBlockX()); signNbt.put("y", loc.getBlockY()); signNbt.put("z", loc.getBlockZ()); tileEntityData.getBlockPositionModifier().write(0, blockPosition); tileEntityData.getIntegers().write(0, 9); tileEntityData.getNbtModifier().write(0, signNbt); try { MythicDungeons.protocolManager.sendServerPacket(player, tileEntityData); MythicDungeons.protocolManager.sendServerPacket(player, openSignEditor); } catch (InvocationTargetException e) { e.printStackTrace(); } } }
I would try to abstract the sign input. When i try to get user input via chat/anvil/sign i always specify a BiConsumer<Player, Info> or something simmilar. A bit like ChatInput.apply(Player user, String message, BiConsumer<Player, String> input); and use it with a lambda. Pseudocode: Code (Java): public void apply(Player user, String message, BiConsumer<Player, String> infoConsumer) { // Send msg ... // recieve input String input; // consume result infoConsumer.accept(user, unput); } Usage: Code (Java): apply(player, message, (player, input) ->{ // open GUI with new param }); In your case you would have List<String> as data... Another way would be creating a data class that encapsulates the player with the result. Abstract: Code (Java): @Data public interface PlayerInputResult<T> { Player getPlayer(); T getData(); } Implementation with String: Code (Java): @AllArgsConstructor public class ChatInputResult implements PlayerInputResult<String> { private final Player player; private final String input; @Override public Player getPlayer() { return player; } @Override public String getData() { return input; } } Then you just have to write a method that consumes the ChatInputResult. You can even go a bit further and do something like: Code (Java): public abstract class PlayerInput<T> { public void request(T initialData, Player player, Consumer<PlayerInputResult<T>> resultConsumer) { resultConsumer.accept(produce(initialData, player)); } protected abstract PlayerInputResult<T> produce(T initialData, Player player); } Code (Java): public class ChatInput extends PlayerInput<String> { @Override protected PlayerInputResult<String> produce(String initialData, Player player) { // Get chat input String input; return new ChatInputResult(player, input); } } Abstraction is the key to modular code. PS this wont work as you have to supply some data but the whole idea is using supplier and consumer to create a chain of responsibility
Thanks for the detailed reply but.. this is out of my knowledge and i'm reading java tutorials about this XD Maybe these are dumb questions but it really confuse me. - Will something like this freeze the main thread until the user press "Done" or ESC? Code (Text): // Somewhere in your code SignGui gui = new SignGui() .line(Line.SECOND, "Default line2") // Set line 2 .line(Line.FOURTH, "Default line4") // Set line 4 .color() // Color user input .listener((player, lines) -> // Listen to user input, where player is the submitter and lines a String[4] array player.sendMessage("You entered " + lines[0] + " on line 1.")); (#1 Reference: https://www.spigotmc.org/threads/1-8-signgui-user-input-with-signs.296013/) - AFAIK BiConsumer accepts 2 parameters and returns nothing, BiFunction accepts 2 parameters and return 1 result, so what to determine the use of BiConsumer and BiFunction? (#2 Reference: https://github.com/WesJD/AnvilGUI/blob/master/api/src/main/java/net/wesjd/anvilgui/AnvilGUI.java) - In #1, it uses BiConsumer and #2 it uses BiFunction, what's the main difference between them and causes SignGUI to use BiConsumer and AnvilGUI uses BiFunction? And how about ChatInput?
depends on what the .listener() method does. But this looks right. You might have to do some mapping <UUID, BiConsumer<Player, String>> then listen for the close event, get the player from the map and consume what is on the sign + the player from the event.
I posted the errors and bugs for few times and I just deleted myself because I solved it myself...... Seems I have to use my brain more before asking stupid questions Anyways, this is working code, is there any improvements of this? Code (Java): package to.epac.factorycraft.mythicdungeons.gui.sign; import java.lang.reflect.InvocationTargetException; import java.util.function.BiConsumer; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.block.Block; import org.bukkit.entity.Player; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.events.PacketListener; import com.comphenix.protocol.wrappers.BlockPosition; import com.comphenix.protocol.wrappers.nbt.NbtCompound; import com.comphenix.protocol.wrappers.nbt.NbtFactory; import to.epac.factorycraft.mythicdungeons.MythicDungeons; public class SignGUI { MythicDungeons plugin = MythicDungeons.inst(); String[] display; Location loc; Block block; BiConsumer<Player, String[]> biConsumer; PacketListener listener; public SignGUI line(int line, String text) { if (display == null) display = new String[4]; display[line] = ChatColor.translateAlternateColorCodes('&', text); return this; } public SignGUI biConsumer(BiConsumer<Player, String[]> biConsumer) { this.biConsumer = biConsumer; return this; } public void show(Player player) { loc = player.getLocation(); loc.setY(0); block = loc.getBlock(); player.sendBlockChange(loc, Material.SIGN.createBlockData()); PacketContainer openSignEditor = MythicDungeons.protocolManager.createPacket( PacketType.Play.Server.OPEN_SIGN_EDITOR); BlockPosition blockPosition = new BlockPosition(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); openSignEditor.getBlockPositionModifier().write(0, blockPosition); PacketContainer tileEntityData = MythicDungeons.protocolManager.createPacket( PacketType.Play.Server.TILE_ENTITY_DATA); NbtCompound signNbt = NbtFactory.ofCompound("MythicDungeons"); signNbt.put("Text1", "{\"text\":\"" + display[0] + "\"}"); signNbt.put("Text2", "{\"text\":\"" + display[1] + "\"}"); signNbt.put("Text3", "{\"text\":\"" + display[2] + "\"}"); signNbt.put("Text4", "{\"text\":\"" + display[3] + "\"}"); signNbt.put("id", "minecraft:sign"); signNbt.put("x", loc.getBlockX()); signNbt.put("y", loc.getBlockY()); signNbt.put("z", loc.getBlockZ()); tileEntityData.getBlockPositionModifier().write(0, blockPosition); tileEntityData.getIntegers().write(0, 9); tileEntityData.getNbtModifier().write(0, signNbt); try { MythicDungeons.protocolManager.sendServerPacket(player, tileEntityData); MythicDungeons.protocolManager.sendServerPacket(player, openSignEditor); } catch (InvocationTargetException e) { e.printStackTrace(); } MythicDungeons.protocolManager.addPacketListener(listener = new PacketAdapter(plugin, PacketType.Play.Client.UPDATE_SIGN) { @Override public void onPacketReceiving(PacketEvent event) { PacketContainer packet = event.getPacket(); Player player = event.getPlayer(); String[] input = packet.getStringArrays().read(0); player.sendBlockChange(loc, block.getBlockData()); unregisterListener(); StringBuilder builder = new StringBuilder(); for (int i = 0; i < input.length; i++) { builder.append(input[i]); if (i < input.length - 1) builder.append(" "); } Bukkit.getScheduler().runTask(plugin, (Runnable)() -> { biConsumer.accept(player, input); }); } }); } public void unregisterListener() { MythicDungeons.protocolManager.removePacketListener(listener); } } Usage Code (Java): SignGUI signgui = new SignGUI() .line(0, "") .line(1, "^^^^^^^^^^^^^^^^") .line(2, "Please enter") .line(3, "dungeon type") .biConsumer((player0, lines) -> { if (lines[0].isEmpty()) { player0.sendMessage(ChatColor.RED + "Please enter dungeon type."); open(player0); } else if (!plugin.getDungeonManager().hasDungeonGroup(lines[0])) { player0.sendMessage(ChatColor.RED + "Invalid dungeon type."); open(player0); } else { DungeonGroup group = plugin.getDungeonManager().getDungeonGroup(lines[0]); CreateGUI createGui = new CreateGUI(group, player0.getUniqueId()); createGui.open(player0); } }); signgui.show(player); I'll leave the working code here in case someone like me did lots of searching and didn't find the solution, this might not be the best, but at least working