diff --git a/CONCEPT_FOR_TABS.png b/CONCEPT_FOR_TABS.png new file mode 100644 index 000000000..17a02be71 Binary files /dev/null and b/CONCEPT_FOR_TABS.png differ diff --git a/src/api b/src/api index 29ad8637d..3f8ccc087 160000 --- a/src/api +++ b/src/api @@ -1 +1 @@ -Subproject commit 29ad8637d68f12b3138c5b0c266088147432733b +Subproject commit 3f8ccc087f781a43c2e5a2044f5d859be53d88c8 diff --git a/src/main/java/kamkeel/npcs/command/CloneCommand.java b/src/main/java/kamkeel/npcs/command/CloneCommand.java index 027ff228e..312293ca5 100644 --- a/src/main/java/kamkeel/npcs/command/CloneCommand.java +++ b/src/main/java/kamkeel/npcs/command/CloneCommand.java @@ -13,6 +13,7 @@ import net.minecraft.util.MathHelper; import net.minecraft.world.World; import net.minecraft.world.WorldServer; import noppes.npcs.controllers.ServerCloneController; +import noppes.npcs.controllers.data.CloneFolder; import noppes.npcs.entity.EntityNPCInterface; import java.util.List; @@ -35,20 +36,24 @@ public class CloneCommand extends CommandKamkeelBase { @SubCommand( desc = "Add NPC(s) to clone storage", - usage = " [clonedname]", + usage = " [clonedname]", permission = 4 ) public void add(ICommandSender sender, String[] args) { - int tab = 0; + int tab = -1; + String folder = null; try { tab = Integer.parseInt(args[1]); - if (tab < 0 || tab > 15) { + if (tab < 1 || tab > 15) { sendError(sender, "Tab must be within 1-15"); return; } } catch (NumberFormatException ex) { - sendError(sender, String.format("Tab is not a number: %s", args[1])); - return; + folder = args[1]; + if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) { + sendError(sender, String.format("Unknown folder: %s", folder)); + return; + } } int x = sender.getPlayerCoordinates().posX; @@ -64,32 +69,47 @@ public class CloneCommand extends CommandKamkeelBase { NBTTagCompound compound = new NBTTagCompound(); if (!npc.writeToNBTOptional(compound)) return; - ServerCloneController.Instance.addClone(compound, name, tab); - sendResult(sender, String.format("Added NPC \u00A7e%s\u00A77 to Tab \u00A7b%d\u00A77", name, tab)); + if (folder != null) { + ServerCloneController.Instance.addClone(compound, name, folder); + sendResult(sender, String.format("Added NPC \u00A7e%s\u00A77 to Folder \u00A7b%s\u00A77", name, folder)); + } else { + ServerCloneController.Instance.addClone(compound, name, tab); + sendResult(sender, String.format("Added NPC \u00A7e%s\u00A77 to Tab \u00A7b%d\u00A77", name, tab)); + } } } } @SubCommand( desc = "List NPC from clone storage", - usage = "", + usage = "", permission = 2 ) public void list(ICommandSender sender, String[] args) { sendMessage(sender, "--- Stored NPCs --- (server side)"); - int tab = 0; + int tab = -1; + String folder = null; try { tab = Integer.parseInt(args[1]); - if (tab < 0 || tab > 15) { + if (tab < 1 || tab > 15) { sendError(sender, "Tab must be within 1-15"); return; } } catch (NumberFormatException ex) { - sendError(sender, String.format("Tab is not a number: %s", args[1])); - return; + folder = args[1]; + if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) { + sendError(sender, String.format("Unknown folder: %s", folder)); + return; + } } - for (String name : ServerCloneController.Instance.getClones(tab)) { + List clones; + if (folder != null) { + clones = ServerCloneController.Instance.getClones(folder); + } else { + clones = ServerCloneController.Instance.getClones(tab); + } + for (String name : clones) { sendMessage(sender, name); } sendMessage(sender, "------------------------------------"); @@ -97,59 +117,75 @@ public class CloneCommand extends CommandKamkeelBase { @SubCommand( desc = "Remove NPC from clone storage", - usage = " ", + usage = " ", permission = 4 ) public void del(ICommandSender sender, String[] args) throws CommandException { String nametodel = args[0]; - int tab = 0; + int tab = -1; + String folder = null; try { tab = Integer.parseInt(args[1]); - if (tab < 0 || tab > 15) { - sendError(sender, String.format("Tab must be within 1-15")); + if (tab < 1 || tab > 15) { + sendError(sender, "Tab must be within 1-15"); return; } } catch (NumberFormatException ex) { - sendError(sender, String.format("Tab is not a number: %s", args[1])); - return; - } - - boolean deleted = false; - for (String name : ServerCloneController.Instance.getClones(tab)) { - if (nametodel.equalsIgnoreCase(name)) { - ServerCloneController.Instance.removeClone(name, tab); - deleted = true; - break; + folder = args[1]; + if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) { + sendError(sender, String.format("Unknown folder: %s", folder)); + return; } } - if (!ServerCloneController.Instance.removeClone(nametodel, tab)) { + + boolean success; + if (folder != null) { + success = ServerCloneController.Instance.removeClone(nametodel, folder); + } else { + success = ServerCloneController.Instance.removeClone(nametodel, tab); + } + + if (!success) { sendError(sender, String.format("NPC '%s' was not found", nametodel)); } else { - sendResult(sender, String.format("Removed NPC \u00A7e%s\u00A77 from Tab \u00A7b%d\u00A77", nametodel, tab)); + if (folder != null) { + sendResult(sender, String.format("Removed NPC \u00A7e%s\u00A77 from Folder \u00A7b%s\u00A77", nametodel, folder)); + } else { + sendResult(sender, String.format("Removed NPC \u00A7e%s\u00A77 from Tab \u00A7b%d\u00A77", nametodel, tab)); + } } } @SubCommand( desc = "Spawn cloned NPC", - usage = " [[world:]x,y,z]] [newname]", + usage = " [[world:]x,y,z]] [newname]", permission = 2 ) public void spawn(ICommandSender sender, String[] args) throws CommandException { String name = args[0].replaceAll("%", " "); // if name of npc separed by space, user must use % in place of space - int tab = 0; + int tab = -1; + String folder = null; try { tab = Integer.parseInt(args[1]); - if (tab < 0 || tab > 15) { - sendError(sender, String.format("Tab must be within 1-15")); + if (tab < 1 || tab > 15) { + sendError(sender, "Tab must be within 1-15"); return; } } catch (NumberFormatException ex) { - sendError(sender, String.format("Tab is not a number: %s", args[1])); - return; + folder = args[1]; + if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) { + sendError(sender, String.format("Unknown folder: %s", folder)); + return; + } } String newname = null; - NBTTagCompound compound = ServerCloneController.Instance.getCloneData(sender, name, tab); + NBTTagCompound compound; + if (folder != null) { + compound = ServerCloneController.Instance.getCloneData(sender, name, folder); + } else { + compound = ServerCloneController.Instance.getCloneData(sender, name, tab); + } if (compound == null) { sendError(sender, "Unknown npc"); return; @@ -211,22 +247,26 @@ public class CloneCommand extends CommandKamkeelBase { } @SubCommand( - desc = "Spawn cloned NPC", - usage = " [[world:]x,y,z]] [newname]", + desc = "Spawn cloned NPC in a grid", + usage = " [[world:]x,y,z]] [newname]", permission = 2 ) public void grid(ICommandSender sender, String[] args) throws CommandException { String name = args[0].replaceAll("%", " "); // if name of npc separed by space, user must use % in place of space - int tab = 0; + int tab = -1; + String folder = null; try { tab = Integer.parseInt(args[1]); - if (tab < 0 || tab > 15) { - sendError(sender, String.format("Tab must be within 1-15")); + if (tab < 1 || tab > 15) { + sendError(sender, "Tab must be within 1-15"); return; } } catch (NumberFormatException ex) { - sendError(sender, String.format("Tab is not a number: %s", args[1])); - return; + folder = args[1]; + if (ServerCloneController.Instance == null || !ServerCloneController.Instance.hasFolder(folder)) { + sendError(sender, String.format("Unknown folder: %s", folder)); + return; + } } int width, height; @@ -238,9 +278,13 @@ public class CloneCommand extends CommandKamkeelBase { return; } - String newname = null; - NBTTagCompound compound = ServerCloneController.Instance.getCloneData(sender, name, tab); + NBTTagCompound compound; + if (folder != null) { + compound = ServerCloneController.Instance.getCloneData(sender, name, folder); + } else { + compound = ServerCloneController.Instance.getCloneData(sender, name, tab); + } if (compound == null) { sendError(sender, "Unknown npc"); return; @@ -317,6 +361,163 @@ public class CloneCommand extends CommandKamkeelBase { return; } + @SubCommand( + desc = "List all custom clone folders", + usage = "", + permission = 2 + ) + public void listfolders(ICommandSender sender, String[] args) { + if (ServerCloneController.Instance == null) { + sendError(sender, "Folder system not initialized"); + return; + } + sendMessage(sender, "--- Clone Folders ---"); + for (CloneFolder f : ServerCloneController.Instance.getFolderList()) { + sendMessage(sender, f.name); + } + sendMessage(sender, "------------------------------------"); + } + + @SubCommand( + desc = "Create a custom clone folder", + usage = "", + permission = 4 + ) + public void createfolder(ICommandSender sender, String[] args) { + if (ServerCloneController.Instance == null) { + sendError(sender, "Folder system not initialized"); + return; + } + String name = args[0]; + if (!CloneFolder.isValidName(name)) { + sendError(sender, String.format("Invalid folder name: %s", name)); + return; + } + if (ServerCloneController.Instance.hasFolder(name)) { + sendError(sender, String.format("Folder '%s' already exists", name)); + return; + } + CloneFolder created = ServerCloneController.Instance.createFolder(name); + if (created != null) { + sendResult(sender, String.format("Created folder \u00A7b%s", name)); + } else { + sendError(sender, String.format("Failed to create folder '%s'", name)); + } + } + + @SubCommand( + desc = "Rename a custom clone folder", + usage = " ", + permission = 4 + ) + public void renamefolder(ICommandSender sender, String[] args) { + if (ServerCloneController.Instance == null) { + sendError(sender, "Folder system not initialized"); + return; + } + String oldName = args[0]; + String newName = args[1]; + if (!ServerCloneController.Instance.hasFolder(oldName)) { + sendError(sender, String.format("Unknown folder: %s", oldName)); + return; + } + if (!CloneFolder.isValidName(newName)) { + sendError(sender, String.format("Invalid folder name: %s", newName)); + return; + } + if (ServerCloneController.Instance.renameFolder(oldName, newName)) { + sendResult(sender, String.format("Renamed folder \u00A7b%s\u00A77 to \u00A7b%s", oldName, newName)); + } else { + sendError(sender, String.format("Failed to rename folder '%s'", oldName)); + } + } + + @SubCommand( + desc = "Delete an empty custom clone folder", + usage = "", + permission = 4 + ) + public void deletefolder(ICommandSender sender, String[] args) { + if (ServerCloneController.Instance == null) { + sendError(sender, "Folder system not initialized"); + return; + } + String name = args[0]; + if (!ServerCloneController.Instance.hasFolder(name)) { + sendError(sender, String.format("Unknown folder: %s", name)); + return; + } + if (ServerCloneController.Instance.deleteFolder(name)) { + sendResult(sender, String.format("Deleted folder \u00A7b%s", name)); + } else { + sendError(sender, String.format("Failed to delete folder '%s' (folder must be empty)", name)); + } + } + + @SubCommand( + desc = "Move a clone between tabs/folders", + usage = " ", + permission = 4 + ) + public void move(ICommandSender sender, String[] args) { + if (ServerCloneController.Instance == null) { + sendError(sender, "Folder system not initialized"); + return; + } + String cloneName = args[0]; + + int fromTab = -1; + String fromFolder = null; + try { + fromTab = Integer.parseInt(args[1]); + if (fromTab < 1 || fromTab > 15) { + sendError(sender, "Source tab must be within 1-15"); + return; + } + } catch (NumberFormatException ex) { + fromFolder = args[1]; + if (!ServerCloneController.Instance.hasFolder(fromFolder)) { + sendError(sender, String.format("Unknown source folder: %s", fromFolder)); + return; + } + } + + int toTab = -1; + String toFolder = null; + try { + toTab = Integer.parseInt(args[2]); + if (toTab < 1 || toTab > 15) { + sendError(sender, "Destination tab must be within 1-15"); + return; + } + } catch (NumberFormatException ex) { + toFolder = args[2]; + if (!ServerCloneController.Instance.hasFolder(toFolder)) { + sendError(sender, String.format("Unknown destination folder: %s", toFolder)); + return; + } + } + + boolean success; + if (fromFolder != null && toFolder != null) { + success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toFolder); + } else if (fromFolder != null) { + success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toTab); + } else if (toFolder != null) { + success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toFolder); + } else { + success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toTab); + } + + if (success) { + String from = fromFolder != null ? fromFolder : String.valueOf(fromTab); + String to = toFolder != null ? toFolder : String.valueOf(toTab); + sendResult(sender, String.format("Moved \u00A7e%s\u00A77 from \u00A7b%s\u00A77 to \u00A7b%s", cloneName, from, to)); + } else { + sendError(sender, String.format("Failed to move '%s'", cloneName)); + } + } + public World getWorld(String t) { WorldServer[] ws = MinecraftServer.getServer().worldServers; for (WorldServer w : ws) { diff --git a/src/main/java/kamkeel/npcs/network/PacketHandler.java b/src/main/java/kamkeel/npcs/network/PacketHandler.java index 63ab9b192..bcfd18abd 100644 --- a/src/main/java/kamkeel/npcs/network/PacketHandler.java +++ b/src/main/java/kamkeel/npcs/network/PacketHandler.java @@ -108,6 +108,9 @@ import kamkeel.npcs.network.packets.request.bank.BankSavePacket; import kamkeel.npcs.network.packets.request.bank.BanksGetPacket; import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket; import kamkeel.npcs.network.packets.request.clone.CloneAllTagsShortPacket; +import kamkeel.npcs.network.packets.request.clone.CloneFolderCrudPacket; +import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket; +import kamkeel.npcs.network.packets.request.clone.CloneMovePacket; import kamkeel.npcs.network.packets.request.clone.CloneListPacket; import kamkeel.npcs.network.packets.request.clone.ClonePreSavePacket; import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket; @@ -325,6 +328,9 @@ public class PacketHandler { REQUEST_PACKET.registerPacket(new CloneTagListPacket()); REQUEST_PACKET.registerPacket(new CloneAllTagsPacket()); REQUEST_PACKET.registerPacket(new CloneAllTagsShortPacket()); + REQUEST_PACKET.registerPacket(new CloneFolderListPacket()); + REQUEST_PACKET.registerPacket(new CloneFolderCrudPacket()); + REQUEST_PACKET.registerPacket(new CloneMovePacket()); // Linked Packets diff --git a/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java b/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java index 8183941d0..2f3e6ea94 100644 --- a/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java +++ b/src/main/java/kamkeel/npcs/network/enums/EnumRequestPacket.java @@ -163,6 +163,9 @@ public enum EnumRequestPacket { CloneAllTags, CloneAllTagsShort, ClonePreSave, + CloneFolderList, + CloneFolderCrud, + CloneMove, EventScript, PlayerScript, diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderCrudPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderCrudPacket.java new file mode 100644 index 000000000..061700e35 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderCrudPacket.java @@ -0,0 +1,111 @@ +package kamkeel.npcs.network.packets.request.clone; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; +import kamkeel.npcs.network.enums.EnumItemPacketType; +import kamkeel.npcs.network.enums.EnumRequestPacket; +import kamkeel.npcs.network.packets.data.large.GuiDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.CustomNpcsPermissions; +import noppes.npcs.controllers.ServerCloneController; +import noppes.npcs.controllers.data.CloneFolder; + +import java.io.IOException; + +public final class CloneFolderCrudPacket extends AbstractPacket { + public static String packetName = "Request|CloneFolderCrud"; + + public static final byte ACTION_CREATE = 0; + public static final byte ACTION_RENAME = 1; + public static final byte ACTION_DELETE = 2; + + private byte action; + private String name; + private String newName; + + public CloneFolderCrudPacket() { + } + + public CloneFolderCrudPacket(byte action, String name) { + this.action = action; + this.name = name; + this.newName = ""; + } + + public CloneFolderCrudPacket(byte action, String name, String newName) { + this.action = action; + this.name = name; + this.newName = newName; + } + + @Override + public Enum getType() { + return EnumRequestPacket.CloneFolderCrud; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.REQUEST_PACKET; + } + + @Override + public CustomNpcsPermissions.Permission getPermission() { + return CustomNpcsPermissions.NPC_CLONE; + } + + @SideOnly(Side.CLIENT) + @Override + public void sendData(ByteBuf out) throws IOException { + out.writeByte(action); + ByteBufUtils.writeString(out, name); + if (action == ACTION_RENAME) { + ByteBufUtils.writeString(out, newName); + } + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP)) + return; + if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER)) + return; + + byte action = in.readByte(); + String name = ByteBufUtils.readString(in); + + boolean success = false; + switch (action) { + case ACTION_CREATE: + if (CloneFolder.isValidName(name)) + success = ServerCloneController.Instance.createFolder(name) != null; + break; + case ACTION_RENAME: + String newName = ByteBufUtils.readString(in); + if (CloneFolder.isValidName(newName)) + success = ServerCloneController.Instance.renameFolder(name, newName); + break; + case ACTION_DELETE: + success = ServerCloneController.Instance.deleteFolder(name); + break; + } + + NBTTagCompound compound = new NBTTagCompound(); + compound.setBoolean("Success", success); + NBTTagList folderList = new NBTTagList(); + for (CloneFolder folder : ServerCloneController.Instance.getFolderList()) { + folderList.appendTag(folder.writeNBT(new NBTTagCompound())); + } + compound.setTag("CloneFolders", folderList); + + GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderListPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderListPacket.java new file mode 100644 index 000000000..b7e7fe9f8 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneFolderListPacket.java @@ -0,0 +1,59 @@ +package kamkeel.npcs.network.packets.request.clone; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; +import kamkeel.npcs.network.enums.EnumItemPacketType; +import kamkeel.npcs.network.enums.EnumRequestPacket; +import kamkeel.npcs.network.packets.data.large.GuiDataPacket; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import noppes.npcs.controllers.ServerCloneController; +import noppes.npcs.controllers.data.CloneFolder; + +import java.io.IOException; + +public final class CloneFolderListPacket extends AbstractPacket { + public static String packetName = "Request|CloneFolderList"; + + public CloneFolderListPacket() { + } + + @Override + public Enum getType() { + return EnumRequestPacket.CloneFolderList; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.REQUEST_PACKET; + } + + @SideOnly(Side.CLIENT) + @Override + public void sendData(ByteBuf out) throws IOException { + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP)) + return; + if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER)) + return; + + NBTTagCompound compound = new NBTTagCompound(); + NBTTagList folderList = new NBTTagList(); + for (CloneFolder folder : ServerCloneController.Instance.getFolderList()) { + folderList.appendTag(folder.writeNBT(new NBTTagCompound())); + } + compound.setTag("CloneFolders", folderList); + + GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneListPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneListPacket.java index 87bac4a29..e6ce35f40 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneListPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneListPacket.java @@ -23,9 +23,16 @@ public final class CloneListPacket extends AbstractPacket { public static String packetName = "Request|CloneList"; private int tab; + private String folderName; public CloneListPacket(int tab) { this.tab = tab; + this.folderName = null; + } + + public CloneListPacket(String folderName) { + this.tab = -1; + this.folderName = folderName; } public CloneListPacket() { @@ -45,6 +52,9 @@ public final class CloneListPacket extends AbstractPacket { @Override public void sendData(ByteBuf out) throws IOException { out.writeInt(tab); + if (tab == -1) { + kamkeel.npcs.util.ByteBufUtils.writeString(out, folderName); + } } @Override @@ -54,14 +64,26 @@ public final class CloneListPacket extends AbstractPacket { if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.MOUNTER, EnumItemPacketType.CLONER)) return; - NBTTagList list = new NBTTagList(); int tab = in.readInt(); - for (String name : ServerCloneController.Instance.getClones(tab)) - list.appendTag(new NBTTagString(name)); + String folder = null; + if (tab == -1) { + folder = kamkeel.npcs.util.ByteBufUtils.readString(in); + } + NBTTagList list = new NBTTagList(); NBTTagList listDate = new NBTTagList(); - for (String name : ServerCloneController.Instance.getClonesDate(tab)) - listDate.appendTag(new NBTTagString(name)); + + if (folder != null) { + for (String name : ServerCloneController.Instance.getClones(folder)) + list.appendTag(new NBTTagString(name)); + for (String name : ServerCloneController.Instance.getClonesDate(folder)) + listDate.appendTag(new NBTTagString(name)); + } else { + for (String name : ServerCloneController.Instance.getClones(tab)) + list.appendTag(new NBTTagString(name)); + for (String name : ServerCloneController.Instance.getClonesDate(tab)) + listDate.appendTag(new NBTTagString(name)); + } NBTTagCompound compound = new NBTTagCompound(); compound.setTag("List", list); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneMovePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneMovePacket.java new file mode 100644 index 000000000..124bd5204 --- /dev/null +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneMovePacket.java @@ -0,0 +1,97 @@ +package kamkeel.npcs.network.packets.request.clone; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import kamkeel.npcs.network.AbstractPacket; +import kamkeel.npcs.network.PacketChannel; +import kamkeel.npcs.network.PacketHandler; +import kamkeel.npcs.network.PacketUtil; +import kamkeel.npcs.network.enums.EnumItemPacketType; +import kamkeel.npcs.network.enums.EnumRequestPacket; +import kamkeel.npcs.network.packets.data.large.GuiDataPacket; +import kamkeel.npcs.util.ByteBufUtils; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import noppes.npcs.CustomNpcsPermissions; +import noppes.npcs.controllers.ServerCloneController; + +import java.io.IOException; + +public final class CloneMovePacket extends AbstractPacket { + public static String packetName = "Request|CloneMove"; + + private String cloneName; + private int fromTab; + private String fromFolder; + private int toTab; + private String toFolder; + + public CloneMovePacket() { + } + + public CloneMovePacket(String cloneName, int fromTab, String fromFolder, int toTab, String toFolder) { + this.cloneName = cloneName; + this.fromTab = fromTab; + this.fromFolder = fromFolder; + this.toTab = toTab; + this.toFolder = toFolder; + } + + @Override + public Enum getType() { + return EnumRequestPacket.CloneMove; + } + + @Override + public PacketChannel getChannel() { + return PacketHandler.REQUEST_PACKET; + } + + @Override + public CustomNpcsPermissions.Permission getPermission() { + return CustomNpcsPermissions.NPC_CLONE; + } + + @SideOnly(Side.CLIENT) + @Override + public void sendData(ByteBuf out) throws IOException { + ByteBufUtils.writeString(out, cloneName); + out.writeInt(fromTab); + ByteBufUtils.writeString(out, fromFolder != null ? fromFolder : ""); + out.writeInt(toTab); + ByteBufUtils.writeString(out, toFolder != null ? toFolder : ""); + } + + @Override + public void receiveData(ByteBuf in, EntityPlayer player) throws IOException { + if (!(player instanceof EntityPlayerMP)) + return; + if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER)) + return; + + String cloneName = ByteBufUtils.readString(in); + int fromTab = in.readInt(); + String fromFolder = ByteBufUtils.readString(in); + if (fromFolder.isEmpty()) fromFolder = null; + int toTab = in.readInt(); + String toFolder = ByteBufUtils.readString(in); + if (toFolder.isEmpty()) toFolder = null; + + boolean success = false; + if (fromFolder != null && toFolder != null) { + success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toFolder); + } else if (fromFolder != null) { + success = ServerCloneController.Instance.moveClone(cloneName, fromFolder, toTab); + } else if (toFolder != null) { + success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toFolder); + } else { + success = ServerCloneController.Instance.moveClone(cloneName, fromTab, toTab); + } + + NBTTagCompound compound = new NBTTagCompound(); + compound.setBoolean("MoveSuccess", success); + GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); + } +} diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/ClonePreSavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/ClonePreSavePacket.java index 378145ddb..4d36eb531 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/ClonePreSavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/ClonePreSavePacket.java @@ -24,6 +24,7 @@ public final class ClonePreSavePacket extends AbstractPacket { private String name; private int tab; + private String folderName; public ClonePreSavePacket() { } @@ -31,6 +32,13 @@ public final class ClonePreSavePacket extends AbstractPacket { public ClonePreSavePacket(String name, int tab) { this.name = name; this.tab = tab; + this.folderName = null; + } + + public ClonePreSavePacket(String name, String folderName) { + this.name = name; + this.tab = -1; + this.folderName = folderName; } @Override @@ -53,6 +61,9 @@ public final class ClonePreSavePacket extends AbstractPacket { public void sendData(ByteBuf out) throws IOException { ByteBufUtils.writeString(out, this.name); out.writeInt(tab); + if (tab == -1) { + ByteBufUtils.writeString(out, this.folderName); + } } @Override @@ -62,7 +73,15 @@ public final class ClonePreSavePacket extends AbstractPacket { if (!PacketUtil.verifyItemPacket(packetName, player, EnumItemPacketType.CLONER)) return; - boolean bo = ServerCloneController.Instance.getCloneData(null, ByteBufUtils.readString(in), in.readInt()) != null; + String name = ByteBufUtils.readString(in); + int tab = in.readInt(); + boolean bo; + if (tab == -1) { + String folder = ByteBufUtils.readString(in); + bo = ServerCloneController.Instance.getCloneData(null, name, folder) != null; + } else { + bo = ServerCloneController.Instance.getCloneData(null, name, tab) != null; + } NBTTagCompound compound = new NBTTagCompound(); compound.setBoolean("NameExists", bo); GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneRemovePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneRemovePacket.java index ce1fcb367..96069f414 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneRemovePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneRemovePacket.java @@ -25,6 +25,7 @@ public final class CloneRemovePacket extends AbstractPacket { public static String packetName = "Request|CloneRemove"; private int tab; + private String folderName; private String name; public CloneRemovePacket() { @@ -32,6 +33,13 @@ public final class CloneRemovePacket extends AbstractPacket { public CloneRemovePacket(int tab, String name) { this.tab = tab; + this.folderName = null; + this.name = name; + } + + public CloneRemovePacket(String folderName, String name) { + this.tab = -1; + this.folderName = folderName; this.name = name; } @@ -54,6 +62,9 @@ public final class CloneRemovePacket extends AbstractPacket { @Override public void sendData(ByteBuf out) throws IOException { out.writeInt(tab); + if (tab == -1) { + ByteBufUtils.writeString(out, this.folderName); + } ByteBufUtils.writeString(out, this.name); } @@ -65,16 +76,28 @@ public final class CloneRemovePacket extends AbstractPacket { return; int tab = in.readInt(); - ServerCloneController.Instance.removeClone(ByteBufUtils.readString(in), tab); + String folder = null; + if (tab == -1) { + folder = ByteBufUtils.readString(in); + } + String cloneName = ByteBufUtils.readString(in); NBTTagList list = new NBTTagList(); - - for (String name : ServerCloneController.Instance.getClones(tab)) - list.appendTag(new NBTTagString(name)); - NBTTagList listDate = new NBTTagList(); - for (String name : ServerCloneController.Instance.getClonesDate(tab)) - listDate.appendTag(new NBTTagString(name)); + + if (folder != null) { + ServerCloneController.Instance.removeClone(cloneName, folder); + for (String name : ServerCloneController.Instance.getClones(folder)) + list.appendTag(new NBTTagString(name)); + for (String name : ServerCloneController.Instance.getClonesDate(folder)) + listDate.appendTag(new NBTTagString(name)); + } else { + ServerCloneController.Instance.removeClone(cloneName, tab); + for (String name : ServerCloneController.Instance.getClones(tab)) + list.appendTag(new NBTTagString(name)); + for (String name : ServerCloneController.Instance.getClonesDate(tab)) + listDate.appendTag(new NBTTagString(name)); + } NBTTagCompound compound = new NBTTagCompound(); compound.setTag("List", list); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneSavePacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneSavePacket.java index 3a8abd9c0..2c66572e3 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneSavePacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneSavePacket.java @@ -25,6 +25,7 @@ public final class CloneSavePacket extends AbstractPacket { private String name; private int tab; + private String folderName; private NBTTagCompound tagExtra; private NBTTagCompound tagCompound; @@ -34,6 +35,15 @@ public final class CloneSavePacket extends AbstractPacket { public CloneSavePacket(String name, int tab, NBTTagCompound tagExtra, NBTTagCompound tagCompound) { this.name = name; this.tab = tab; + this.folderName = null; + this.tagExtra = tagExtra; + this.tagCompound = tagCompound; + } + + public CloneSavePacket(String name, String folderName, NBTTagCompound tagExtra, NBTTagCompound tagCompound) { + this.name = name; + this.tab = -1; + this.folderName = folderName; this.tagExtra = tagExtra; this.tagCompound = tagCompound; } @@ -58,6 +68,9 @@ public final class CloneSavePacket extends AbstractPacket { public void sendData(ByteBuf out) throws IOException { ByteBufUtils.writeString(out, this.name); out.writeInt(tab); + if (tab == -1) { + ByteBufUtils.writeString(out, this.folderName); + } ByteBufUtils.writeNBT(out, this.tagExtra); ByteBufUtils.writeNBT(out, this.tagCompound); } @@ -75,11 +88,20 @@ public final class CloneSavePacket extends AbstractPacket { String name = ByteBufUtils.readString(in); int tab = in.readInt(); + String folder = null; + if (tab == -1) { + folder = ByteBufUtils.readString(in); + } NBTTagCompound tagExtra = ByteBufUtils.readNBT(in); NBTTagCompound tagCompound = ByteBufUtils.readNBT(in); NBTTagList tagList = tagCompound.getTagList("TagUUIDs", 8); data.cloned.setTag("TagUUIDs", tagList); - ServerCloneController.Instance.addClone(data.cloned, name, tab, tagExtra); + + if (folder != null) { + ServerCloneController.Instance.addClone(data.cloned, name, folder, tagExtra); + } else { + ServerCloneController.Instance.addClone(data.cloned, name, tab, tagExtra); + } } } diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java index 4d7b21aac..926c5519c 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/CloneTagListPacket.java @@ -13,6 +13,7 @@ import kamkeel.npcs.network.packets.data.large.GuiDataPacket; import net.minecraft.entity.player.EntityPlayer; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.nbt.NBTTagCompound; +import kamkeel.npcs.util.ByteBufUtils; import noppes.npcs.controllers.ServerTagMapController; import noppes.npcs.controllers.data.TagMap; @@ -22,9 +23,16 @@ public final class CloneTagListPacket extends AbstractPacket { public static String packetName = "Request|CloneTagList"; private int tab; + private String folderName; public CloneTagListPacket(int tab) { this.tab = tab; + this.folderName = null; + } + + public CloneTagListPacket(String folderName) { + this.tab = -1; + this.folderName = folderName; } public CloneTagListPacket() { @@ -44,6 +52,9 @@ public final class CloneTagListPacket extends AbstractPacket { @Override public void sendData(ByteBuf out) throws IOException { out.writeInt(tab); + if (tab == -1) { + ByteBufUtils.writeString(out, folderName); + } } @Override @@ -54,7 +65,13 @@ public final class CloneTagListPacket extends AbstractPacket { return; int tab = in.readInt(); - TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab); + TagMap tagMap; + if (tab == -1) { + String folder = ByteBufUtils.readString(in); + tagMap = ServerTagMapController.Instance.getTagMap(folder); + } else { + tagMap = ServerTagMapController.Instance.getTagMap(tab); + } NBTTagCompound compound = new NBTTagCompound(); compound.setTag("CloneTags", tagMap.writeNBT()); GuiDataPacket.sendGuiData((EntityPlayerMP) player, compound); diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/MobSpawnerPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/MobSpawnerPacket.java index cc4dfb3ed..0ad817b06 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/MobSpawnerPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/MobSpawnerPacket.java @@ -31,6 +31,7 @@ public class MobSpawnerPacket extends AbstractPacket { private String selectedName; private int tab; + private String folderName; private NBTTagCompound compound; @@ -44,6 +45,17 @@ public class MobSpawnerPacket extends AbstractPacket { this.posz = posz; this.selectedName = selectedName; this.tab = tab; + this.folderName = null; + } + + public MobSpawnerPacket(Action type, int posX, int posY, int posz, String selectedName, String folderName) { + this.type = type; + this.posX = posX; + this.posY = posY; + this.posz = posz; + this.selectedName = selectedName; + this.tab = -1; + this.folderName = folderName; } public MobSpawnerPacket(Action type, int posX, int posY, int posz, NBTTagCompound compound) { @@ -58,6 +70,10 @@ public class MobSpawnerPacket extends AbstractPacket { PacketClient.sendClient(new MobSpawnerPacket(Action.Server, x, y, z, name, tab)); } + public static void ServerFolder(int x, int y, int z, String name, String folderName) { + PacketClient.sendClient(new MobSpawnerPacket(Action.Server, x, y, z, name, folderName)); + } + public static void Client(int x, int y, int z, NBTTagCompound compound) { PacketClient.sendClient(new MobSpawnerPacket(Action.Client, x, y, z, compound)); } @@ -88,6 +104,9 @@ public class MobSpawnerPacket extends AbstractPacket { if (type == Action.Server) { ByteBufUtils.writeString(out, this.selectedName); out.writeInt(this.tab); + if (this.tab == -1) { + ByteBufUtils.writeString(out, this.folderName); + } } else { ByteBufUtils.writeNBT(out, this.compound); } @@ -107,10 +126,18 @@ public class MobSpawnerPacket extends AbstractPacket { int z = in.readInt(); NBTTagCompound compound; - if (requestedAction == Action.Server) - compound = ServerCloneController.Instance.getCloneData(player, ByteBufUtils.readString(in), in.readInt()); - else + if (requestedAction == Action.Server) { + String name = ByteBufUtils.readString(in); + int tab = in.readInt(); + if (tab == -1) { + String folder = ByteBufUtils.readString(in); + compound = ServerCloneController.Instance.getCloneData(player, name, folder); + } else { + compound = ServerCloneController.Instance.getCloneData(player, name, tab); + } + } else { compound = ByteBufUtils.readNBT(in); + } if (!ConfigScript.canScript(player, CustomNpcsPermissions.SCRIPT)) { return; diff --git a/src/main/java/kamkeel/npcs/network/packets/request/clone/SpawnMobPacket.java b/src/main/java/kamkeel/npcs/network/packets/request/clone/SpawnMobPacket.java index b83fb9286..8afde02cf 100644 --- a/src/main/java/kamkeel/npcs/network/packets/request/clone/SpawnMobPacket.java +++ b/src/main/java/kamkeel/npcs/network/packets/request/clone/SpawnMobPacket.java @@ -37,6 +37,7 @@ public class SpawnMobPacket extends AbstractPacket { private String selectedName; private int tab; + private String folderName; private NBTTagCompound compound; @@ -50,6 +51,17 @@ public class SpawnMobPacket extends AbstractPacket { this.posz = posz; this.selectedName = selectedName; this.tab = tab; + this.folderName = null; + } + + public SpawnMobPacket(Action type, int posX, int posY, int posz, String selectedName, String folderName) { + this.type = type; + this.posX = posX; + this.posY = posY; + this.posz = posz; + this.selectedName = selectedName; + this.tab = -1; + this.folderName = folderName; } public SpawnMobPacket(Action type, int posX, int posY, int posz, NBTTagCompound compound) { @@ -64,6 +76,10 @@ public class SpawnMobPacket extends AbstractPacket { PacketClient.sendClient(new SpawnMobPacket(Action.Server, x, y, z, name, tab)); } + public static void ServerFolder(int x, int y, int z, String name, String folderName) { + PacketClient.sendClient(new SpawnMobPacket(Action.Server, x, y, z, name, folderName)); + } + public static void Client(int x, int y, int z, NBTTagCompound compound) { PacketClient.sendClient(new SpawnMobPacket(Action.Client, x, y, z, compound)); } @@ -94,6 +110,9 @@ public class SpawnMobPacket extends AbstractPacket { if (type == Action.Server) { ByteBufUtils.writeString(out, this.selectedName); out.writeInt(this.tab); + if (this.tab == -1) { + ByteBufUtils.writeString(out, this.folderName); + } } else { ByteBufUtils.writeNBT(out, this.compound); } @@ -113,10 +132,18 @@ public class SpawnMobPacket extends AbstractPacket { int z = in.readInt(); NBTTagCompound compound; - if (requestedAction == Action.Server) - compound = ServerCloneController.Instance.getCloneData(player, ByteBufUtils.readString(in), in.readInt()); - else + if (requestedAction == Action.Server) { + String name = ByteBufUtils.readString(in); + int tab = in.readInt(); + if (tab == -1) { + String folder = ByteBufUtils.readString(in); + compound = ServerCloneController.Instance.getCloneData(player, name, folder); + } else { + compound = ServerCloneController.Instance.getCloneData(player, name, tab); + } + } else { compound = ByteBufUtils.readNBT(in); + } if (compound == null) return; diff --git a/src/main/java/noppes/npcs/client/ClientProxy.java b/src/main/java/noppes/npcs/client/ClientProxy.java index bb90e0659..c0c88a433 100644 --- a/src/main/java/noppes/npcs/client/ClientProxy.java +++ b/src/main/java/noppes/npcs/client/ClientProxy.java @@ -76,6 +76,7 @@ import noppes.npcs.client.gui.GuiBorderBlock; import noppes.npcs.client.gui.GuiMerchantAdd; import noppes.npcs.client.gui.GuiNpcDimension; import noppes.npcs.client.gui.GuiNpcMobSpawner; +import noppes.npcs.client.gui.GuiNpcMobSpawnerFullscreen; import noppes.npcs.client.gui.GuiNpcMobSpawnerMounter; import noppes.npcs.client.gui.GuiNpcPather; import noppes.npcs.client.gui.GuiNpcRedstoneBlock; @@ -334,7 +335,6 @@ public class ClientProxy extends CommonProxy { ClientCloneController.Instance = new ClientCloneController(); ClientTagMapController.Instance = new ClientTagMapController(); - MinecraftForge.EVENT_BUS.register(new ClientEventHandler()); // Telegraph rendering system @@ -603,7 +603,9 @@ public class ClientProxy extends CommonProxy { return new GuiNpcRedstoneBlock(x, y, z); else if (gui == EnumGuiType.Cloner) - return new GuiNpcMobSpawner(x, y, z); + return GuiNpcMobSpawner.isFullscreen + ? new GuiNpcMobSpawnerFullscreen(x, y, z) + : new GuiNpcMobSpawner(x, y, z); else if (gui == EnumGuiType.MobSpawnerMounter) return new GuiNpcMobSpawnerMounter(x, y, z); diff --git a/src/main/java/noppes/npcs/client/controllers/ClientCloneController.java b/src/main/java/noppes/npcs/client/controllers/ClientCloneController.java index 81f174170..9e62177f0 100644 --- a/src/main/java/noppes/npcs/client/controllers/ClientCloneController.java +++ b/src/main/java/noppes/npcs/client/controllers/ClientCloneController.java @@ -41,6 +41,26 @@ public class ClientCloneController extends ServerCloneController { return true; } + @Override + public boolean addToTagMap(NBTTagCompound nbttagcompound, String name, String folderName) { + HashSet tagUUIDs = new HashSet<>(); + if (nbttagcompound.hasKey("TagUUIDs")) { + NBTTagList nbtTagList = nbttagcompound.getTagList("TagUUIDs", 8); + for (int i = 0; i < nbtTagList.tagCount(); i++) { + tagUUIDs.add(UUID.fromString(nbtTagList.getStringTagAt(i))); + } + } + + TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName); + if (!tagUUIDs.isEmpty()) { + tagMap.putClone(name, tagUUIDs); + } else { + tagMap.removeClone(name); + } + ClientTagMapController.Instance.saveTagMap(tagMap); + return true; + } + @Override public boolean removeFromTagMap(String name, int tab) { TagMap tagMap = ClientTagMapController.Instance.getTagMap(tab); @@ -51,4 +71,43 @@ public class ClientCloneController extends ServerCloneController { return false; } + @Override + public boolean removeFromTagMap(String name, String folderName) { + TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName); + if (tagMap.removeClone(name)) { + ClientTagMapController.Instance.saveTagMap(tagMap); + return true; + } + return false; + } + + @Override + protected HashSet getTagUUIDsFromTab(String cloneName, int tab) { + TagMap tagMap = ClientTagMapController.Instance.getTagMap(tab); + HashSet uuids = tagMap.getUUIDs(cloneName); + return uuids != null ? new HashSet<>(uuids) : new HashSet<>(); + } + + @Override + protected HashSet getTagUUIDsFromFolder(String cloneName, String folderName) { + TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName); + HashSet uuids = tagMap.getUUIDs(cloneName); + return uuids != null ? new HashSet<>(uuids) : new HashSet<>(); + } + + @Override + protected void setTagUUIDsForTab(String cloneName, int tab, HashSet tagUUIDs) { + if (tagUUIDs == null || tagUUIDs.isEmpty()) return; + TagMap tagMap = ClientTagMapController.Instance.getTagMap(tab); + tagMap.putClone(cloneName, tagUUIDs); + ClientTagMapController.Instance.saveTagMap(tagMap); + } + + @Override + protected void setTagUUIDsForFolder(String cloneName, String folderName, HashSet tagUUIDs) { + if (tagUUIDs == null || tagUUIDs.isEmpty()) return; + TagMap tagMap = ClientTagMapController.Instance.getTagMap(folderName); + tagMap.putClone(cloneName, tagUUIDs); + ClientTagMapController.Instance.saveTagMap(tagMap); + } } diff --git a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java index b3670d9e8..7fd5c174e 100644 --- a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java +++ b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawner.java @@ -2,6 +2,7 @@ package noppes.npcs.client.gui; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket; +import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket; import kamkeel.npcs.network.packets.request.clone.CloneListPacket; import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket; import kamkeel.npcs.network.packets.request.clone.CloneTagListPacket; @@ -26,6 +27,8 @@ import noppes.npcs.client.gui.util.GuiNPCInterface; import noppes.npcs.client.gui.util.GuiNpcButton; import noppes.npcs.client.gui.util.GuiNpcLabel; import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.IClonerGui; +import noppes.npcs.client.gui.util.ICustomScrollListener; import noppes.npcs.client.gui.util.IGuiData; import noppes.npcs.controllers.data.Tag; import noppes.npcs.controllers.data.TagMap; @@ -38,52 +41,84 @@ import java.util.List; import java.util.Map; import java.util.UUID; -public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { +public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData, ICustomScrollListener, IClonerGui { + + // ==================== STATIC STATE (persists across GUI opens, shared with fullscreen) ==================== + public static boolean isFullscreen = false; + public static int activeTab = 1; + public static String activeFolder = null; + public static int showingClones = 0; // 0=Clones, 1=Entities, 2=Server, 3=Filters + public static String search = ""; + public static String tagSearch = ""; + public static HashMap tags = new HashMap<>(); public static HashMap tagNames = new HashMap<>(); public static HashSet tagFilters = new HashSet<>(); public static byte displayTags = 0; - // 0 - Any, 1 - All, 2 - Not Any, 3 - Not All public static byte filterCondition = 0; - public static byte ascending = 0; - // 0 - By Name, 1 - By Date public static byte sortType = 0; - public TagMap tagMap; + // Scroll position preservation (shared with fullscreen) + public static int savedContentScrollY = 0; + public static int savedNavScrollY = 0; + // ==================== INSTANCE STATE ==================== + public TagMap tagMap; private final GuiCustomScrollCloner scroll = new GuiCustomScrollCloner(this, 0); private final GuiCustomScroll filterScroll = new GuiCustomScroll(this, 1); - private int posX, posY, posZ; - + public int posX, posY, posZ; private List list; private List rawList; private List tagList; - private static int showingClones = 0; - - private static String search = ""; - private static String tagSearch = ""; - - private int activeTab = 1; - + // ==================== CONSTRUCTOR ==================== public GuiNpcMobSpawner(int i, int j, int k) { super(); - xSize = 354; posX = i; posY = j; posZ = k; - - this.closeOnEsc = true; - + closeOnEsc = true; + xSize = 354; setBackground("menubg.png"); PacketClient.sendClient(new CloneAllTagsPacket()); + if (showingClones == 2) { + PacketClient.sendClient(new CloneFolderListPacket()); + } + + // Minimized view doesn't support folders — ensure we're on a tab + if (activeFolder != null) { + if (activeTab < 1 || activeTab > 15) { + activeTab = 1; + } + activeFolder = null; + } } + // ==================== IClonerGui ==================== + @Override + public int getShowingClones() { + return showingClones; + } + + @Override + public HashMap getTags() { + return tags; + } + + @Override + public TagMap getTagMap() { + return tagMap; + } + + // ==================== INIT GUI ==================== public void initGui() { + // Save scroll position before clear + savedContentScrollY = scroll.scrollY; + super.initGui(); guiTop += 10; guiLeft += 30; @@ -92,6 +127,7 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { scroll.setSize(293, 188); scroll.guiLeft = guiLeft + 4; scroll.guiTop = guiTop + 26; + filterScroll.clear(); filterScroll.setSize(140, 166); filterScroll.guiLeft = guiLeft + 4; @@ -99,112 +135,133 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { filterScroll.multipleSelection = true; filterScroll.setSelectedList(tagFilters); - GuiMenuTopButton button; - addTopButton(button = new GuiMenuTopButton(3, guiLeft + 4, guiTop - 17, "spawner.clones")); - button.active = showingClones == 0; - addTopButton(button = new GuiMenuTopButton(5, button, "gui.server")); - button.active = showingClones == 2; - addTopButton(button = new GuiMenuTopButton(4, button, "spawner.entities")); - button.active = showingClones == 1; - addTopButton(button = new GuiMenuTopButton(16, guiLeft + (xSize - 67), guiTop - 17, "gui.filters")); - button.active = showingClones == 3; - addTopButton(button = new GuiMenuTopButton(17, guiLeft + (xSize - 22), guiTop - 17, "X")); + // Top buttons — build right-to-left to avoid overlap + GuiMenuTopButton closeBtn = new GuiMenuTopButton(17, guiLeft + (xSize - 22), guiTop - 17, "X"); + addTopButton(closeBtn); + + // Position "+" dynamically to the left of "X" + int plusX = closeBtn.xPosition - (fontRendererObj.getStringWidth("+") + 12) - 2; + GuiMenuTopButton fullscreenBtn = new GuiMenuTopButton(61, plusX, guiTop - 17, "+"); + addTopButton(fullscreenBtn); + + // Position "Filters" dynamically to the left of "+" + int filterBtnW = fontRendererObj.getStringWidth(StatCollector.translateToLocal("gui.filters")) + 12; + int filterX = fullscreenBtn.xPosition - filterBtnW - 2; + GuiMenuTopButton filterBtn = new GuiMenuTopButton(16, filterX, guiTop - 17, "gui.filters"); + filterBtn.active = showingClones == 3; + addTopButton(filterBtn); + + // Mode buttons from left + GuiMenuTopButton btn; + addTopButton(btn = new GuiMenuTopButton(3, guiLeft + 4, guiTop - 17, "spawner.clones")); + btn.active = showingClones == 0; + addTopButton(btn = new GuiMenuTopButton(5, btn, "gui.server")); + btn.active = showingClones == 2; + addTopButton(btn = new GuiMenuTopButton(4, btn, "spawner.entities")); + btn.active = showingClones == 1; if (showingClones < 3) { addScroll(scroll); addTextField(new GuiNpcTextField(1, this, fontRendererObj, guiLeft + 4, guiTop + 4, 293, 20, search)); + addButton(new GuiNpcButton(1, guiLeft + 298, guiTop + 6, 52, 20, "item.monsterPlacer.name")); addButton(new GuiNpcButton(2, guiLeft + 298, guiTop + 140, 52, 20, "spawner.mobspawner")); if (showingClones == 0 || showingClones == 2) { - - addSideButton(new GuiMenuSideButton(21, guiLeft - 70, this.guiTop + 2, 70, 22, "1")); - addSideButton(new GuiMenuSideButton(22, guiLeft - 70, this.guiTop + 23, 70, 22, "2")); - addSideButton(new GuiMenuSideButton(23, guiLeft - 70, this.guiTop + 44, 70, 22, "3")); - addSideButton(new GuiMenuSideButton(24, guiLeft - 70, this.guiTop + 65, 70, 22, "4")); - addSideButton(new GuiMenuSideButton(25, guiLeft - 70, this.guiTop + 86, 70, 22, "5")); - addSideButton(new GuiMenuSideButton(26, guiLeft - 70, this.guiTop + 107, 35, 22, "6")); - addSideButton(new GuiMenuSideButton(27, guiLeft - 35, this.guiTop + 107, 35, 22, "7")); - addSideButton(new GuiMenuSideButton(28, guiLeft - 70, this.guiTop + 128, 35, 22, "8")); - addSideButton(new GuiMenuSideButton(29, guiLeft - 35, this.guiTop + 128, 35, 22, "9")); - addSideButton(new GuiMenuSideButton(30, guiLeft - 70, this.guiTop + 149, 35, 22, "10")); - addSideButton(new GuiMenuSideButton(31, guiLeft - 35, this.guiTop + 149, 35, 22, "11")); - addSideButton(new GuiMenuSideButton(32, guiLeft - 70, this.guiTop + 170, 35, 22, "12")); - addSideButton(new GuiMenuSideButton(33, guiLeft - 35, this.guiTop + 170, 35, 22, "13")); - addSideButton(new GuiMenuSideButton(34, guiLeft - 70, this.guiTop + 191, 35, 22, "14")); - addSideButton(new GuiMenuSideButton(35, guiLeft - 35, this.guiTop + 191, 35, 22, "15")); - addButton(new GuiNpcButton(6, guiLeft + 298, guiTop + 190, 52, 20, "gui.remove")); - getSideButton(20 + activeTab).active = true; + initTabSideButtons(); showClones(); + + // Restore scroll position + scroll.scrollY = Math.min(savedContentScrollY, Math.max(0, scroll.maxScrollY)); } else { showEntities(); + scroll.scrollY = Math.min(savedContentScrollY, Math.max(0, scroll.maxScrollY)); } } else { - // Show Filters - addLabel(new GuiNpcLabel(1, StatCollector.translateToLocal("cloner.tagFilters"), guiLeft + 7, guiTop + 7)); - addScroll(filterScroll); - addTextField(new GuiNpcTextField(2, this, fontRendererObj, guiLeft + 4, guiTop + 190, 140, 20, tagSearch)); - - addLabel(new GuiNpcLabel(2, StatCollector.translateToLocal("cloner.tagVisibility"), guiLeft + 150, guiTop + 27)); - addButton(new GuiNpcButton(12, guiLeft + 215, guiTop + 20, new String[]{"display.show", "display.all", "display.hide"}, displayTags)); - - addLabel(new GuiNpcLabel(3, StatCollector.translateToLocal("filter.contains"), guiLeft + 150, guiTop + 50)); - addButton(new GuiNpcButton(13, guiLeft + 215, guiTop + 43, new String[]{"filter.any", "filter.all", "filter.notany", "filter.notall"}, filterCondition)); - - addButton(new GuiNpcButton(11, guiLeft + 150, guiTop + 66, 130, 20, "gui.deselectAll")); - - - addLabel(new GuiNpcLabel(4, StatCollector.translateToLocal("cloner.order"), guiLeft + 150, guiTop + 112)); - addButton(new GuiNpcButton(14, guiLeft + 215, guiTop + 105, new String[]{"cloner.ascending", "cloner.descending"}, ascending)); - - addLabel(new GuiNpcLabel(5, StatCollector.translateToLocal("cloner.type"), guiLeft + 150, guiTop + 135)); - addButton(new GuiNpcButton(15, guiLeft + 215, guiTop + 128, new String[]{"cloner.name", "cloner.date"}, sortType)); - - - getButton(12).width = 65; - getButton(12).height = 20; - - getButton(13).width = 65; - getButton(13).height = 20; - - getButton(14).width = 65; - getButton(14).height = 20; - - getButton(15).width = 65; - getButton(15).height = 20; - - showFilters(); + showFiltersPage(); } } - public int getShowingClones() { - return showingClones; + private void initTabSideButtons() { + // Tabs 1-5: full width (70px) + addSideButton(new GuiMenuSideButton(21, guiLeft - 70, guiTop + 2, 70, 22, "1")); + addSideButton(new GuiMenuSideButton(22, guiLeft - 70, guiTop + 23, 70, 22, "2")); + addSideButton(new GuiMenuSideButton(23, guiLeft - 70, guiTop + 44, 70, 22, "3")); + addSideButton(new GuiMenuSideButton(24, guiLeft - 70, guiTop + 65, 70, 22, "4")); + addSideButton(new GuiMenuSideButton(25, guiLeft - 70, guiTop + 86, 70, 22, "5")); + // Tabs 6-15: half width (35px), 2-column grid + addSideButton(new GuiMenuSideButton(26, guiLeft - 70, guiTop + 107, 35, 22, "6")); + addSideButton(new GuiMenuSideButton(27, guiLeft - 35, guiTop + 107, 35, 22, "7")); + addSideButton(new GuiMenuSideButton(28, guiLeft - 70, guiTop + 128, 35, 22, "8")); + addSideButton(new GuiMenuSideButton(29, guiLeft - 35, guiTop + 128, 35, 22, "9")); + addSideButton(new GuiMenuSideButton(30, guiLeft - 70, guiTop + 149, 35, 22, "10")); + addSideButton(new GuiMenuSideButton(31, guiLeft - 35, guiTop + 149, 35, 22, "11")); + addSideButton(new GuiMenuSideButton(32, guiLeft - 70, guiTop + 170, 35, 22, "12")); + addSideButton(new GuiMenuSideButton(33, guiLeft - 35, guiTop + 170, 35, 22, "13")); + addSideButton(new GuiMenuSideButton(34, guiLeft - 70, guiTop + 191, 35, 22, "14")); + addSideButton(new GuiMenuSideButton(35, guiLeft - 35, guiTop + 191, 35, 22, "15")); + + if (activeTab >= 1 && activeTab <= 15) { + GuiMenuSideButton active = getSideButton(20 + activeTab); + if (active != null) active.active = true; + } } - public void showFilters() { + // ==================== FILTERS PAGE ==================== + private void showFiltersPage() { + int baseY = guiTop; + + addLabel(new GuiNpcLabel(1, StatCollector.translateToLocal("cloner.tagFilters"), + guiLeft + 7, baseY + 7)); + + filterScroll.clear(); + filterScroll.setSize(140, 166); + filterScroll.guiLeft = guiLeft + 4; + filterScroll.guiTop = baseY + 19; + filterScroll.multipleSelection = true; + filterScroll.setSelectedList(tagFilters); + addScroll(filterScroll); + + addTextField(new GuiNpcTextField(2, this, fontRendererObj, + guiLeft + 4, baseY + 190, 140, 20, tagSearch)); + + addLabel(new GuiNpcLabel(2, StatCollector.translateToLocal("cloner.tagVisibility"), + guiLeft + 150, baseY + 27)); + addButton(new GuiNpcButton(12, guiLeft + 215, baseY + 20, + new String[]{"display.show", "display.all", "display.hide"}, displayTags)); + + addLabel(new GuiNpcLabel(3, StatCollector.translateToLocal("filter.contains"), + guiLeft + 150, baseY + 50)); + addButton(new GuiNpcButton(13, guiLeft + 215, baseY + 43, + new String[]{"filter.any", "filter.all", "filter.notany", "filter.notall"}, filterCondition)); + + addButton(new GuiNpcButton(11, guiLeft + 150, baseY + 66, 130, 20, "gui.deselectAll")); + + addLabel(new GuiNpcLabel(4, StatCollector.translateToLocal("cloner.order"), + guiLeft + 150, baseY + 112)); + addButton(new GuiNpcButton(14, guiLeft + 215, baseY + 105, + new String[]{"cloner.ascending", "cloner.descending"}, ascending)); + + addLabel(new GuiNpcLabel(5, StatCollector.translateToLocal("cloner.type"), + guiLeft + 150, baseY + 135)); + addButton(new GuiNpcButton(15, guiLeft + 215, baseY + 128, + new String[]{"cloner.name", "cloner.date"}, sortType)); + + getButton(12).width = 65; + getButton(12).height = 20; + getButton(13).width = 65; + getButton(13).height = 20; + getButton(14).width = 65; + getButton(14).height = 20; + getButton(15).width = 65; + getButton(15).height = 20; + filterScroll.setList(getTagList()); } - private void showEntities() { - Map data = EntityList.stringToClassMapping; - ArrayList list = new ArrayList(); - for (Object name : data.keySet()) { - Class c = (Class) data.get(name); - try { - if (EntityLiving.class.isAssignableFrom(c) && c.getConstructor(new Class[]{World.class}) != null && !Modifier.isAbstract(c.getModifiers())) - list.add(name.toString()); - } catch (SecurityException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e) { - } - } - this.list = list; - this.rawList = new ArrayList(this.list); - scroll.setList(getSearchList(), ascending == 0, sortType == 0); - } - + // ==================== CLONE / ENTITY DISPLAY ==================== private void showClones() { if (showingClones == 2) { PacketClient.sendClient(new CloneTagListPacket(activeTab)); @@ -219,16 +276,41 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { } this.tagMap = ClientTagMapController.Instance.getTagMap(activeTab); + populateRawListWithTags(); + scroll.setList(getSearchList(), ascending == 0, sortType == 0); + } + + private void showEntities() { + Map data = EntityList.stringToClassMapping; + ArrayList list = new ArrayList(); + for (Object name : data.keySet()) { + Class c = (Class) data.get(name); + try { + if (EntityLiving.class.isAssignableFrom(c) + && c.getConstructor(new Class[]{World.class}) != null + && !Modifier.isAbstract(c.getModifiers())) + list.add(name.toString()); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + } + } + this.list = list; this.rawList = new ArrayList(this.list); + scroll.setList(getSearchList(), ascending == 0, sortType == 0); + } + + private void populateRawListWithTags() { + this.rawList = new ArrayList<>(this.list); if (this.tagMap != null) { - if (GuiNpcMobSpawner.displayTags == 0 || GuiNpcMobSpawner.displayTags == 1) { + if (displayTags == 0 || displayTags == 1) { for (int i = 0; i < this.list.size(); i++) { StringBuilder npcName = new StringBuilder(list.get(i)); if (this.tagMap.hasClone(list.get(i))) { for (UUID tagUUID : this.tagMap.getUUIDsList(list.get(i))) { Tag tag = tags.get(tagUUID); if (tag != null) { - if (GuiNpcMobSpawner.displayTags == 1 || !tag.getIsHidden()) { + if (displayTags == 1 || !tag.getIsHidden()) { npcName.append(" [" + tag.name + "]"); } } @@ -238,27 +320,26 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { } } } - - scroll.setList(getSearchList(), ascending == 0, sortType == 0); } - public void keyTyped(char c, int i) { - super.keyTyped(c, i); - if (getTextField(1) != null) { - if (search.equals(getTextField(1).getText())) - return; - search = getTextField(1).getText().toLowerCase(); - scroll.setList(getSearchList(), ascending == 0, sortType == 0); - } - if (getTextField(2) != null) { - if (tagSearch.equals(getTextField(2).getText())) - return; - tagSearch = getTextField(2).getText().toLowerCase(); - filterScroll.setList(getTagList()); - } + private NBTTagCompound getCompound() { + String sel = scroll.getSelected(); + if (sel == null) + return null; + if (showingClones == 0) { + return ClientCloneController.Instance.getCloneData(player, sel, activeTab); + } else { + Entity entity = EntityList.createEntityByName(sel, Minecraft.getMinecraft().theWorld); + if (entity == null) + return null; + NBTTagCompound compound = new NBTTagCompound(); + entity.writeToNBTOptional(compound); + return compound; + } } + // ==================== SEARCH / FILTER ==================== private List getSearchList() { if (this.list == null) { this.list = new ArrayList(); @@ -269,10 +350,8 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { // No Filters - or - Entities if (tagFilters.size() == 0 || showingClones == 1) { - // No Search if (search.isEmpty()) return new ArrayList(this.list); - // Yes Search else { List list = new ArrayList(); for (int i = 0; i < this.list.size(); i++) { @@ -284,10 +363,7 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { } } - //////////////////////// - //// WITH FILTERS - - // No Search + // With Filters if (search.isEmpty()) { List list = new ArrayList(); for (String name : this.list) { @@ -300,7 +376,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { return list; } - // Yes Search List list = new ArrayList(); for (int i = 0; i < this.list.size(); i++) { if (this.rawList.get(i).toLowerCase().contains(search)) { @@ -311,7 +386,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { } } } - } return list; } @@ -346,7 +420,6 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { } } } - return conditionMet; } @@ -354,10 +427,8 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { if (this.tagList == null) { this.tagList = new ArrayList(); } - // No Search // No Filters - or - Entities if (tagSearch.isEmpty()) return new ArrayList(tagNames.keySet()); - // Yes Search // No Filters - or - Entities List list = new ArrayList(); for (String name : this.tagList) { @@ -367,125 +438,162 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { return list; } - private NBTTagCompound getCompound() { - String sel = scroll.getSelected(); - if (sel == null) - return null; - - if (showingClones == 0) { - return ClientCloneController.Instance.getCloneData(player, sel, activeTab); - } else { - Entity entity = EntityList.createEntityByName(sel, Minecraft.getMinecraft().theWorld); - if (entity == null) - return null; - NBTTagCompound compound = new NBTTagCompound(); - entity.writeToNBTOptional(compound); - return compound; - } + // ==================== MOUSE / KEYBOARD EVENTS ==================== + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { } + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) { + } + + public void keyTyped(char c, int i) { + super.keyTyped(c, i); + + GuiNpcTextField searchField = getTextField(1); + if (searchField != null) { + String newText = searchField.getText().toLowerCase(); + if (!search.equals(newText)) { + search = newText; + scroll.setList(getSearchList(), ascending == 0, sortType == 0); + } + } + + GuiNpcTextField tagSearchField = getTextField(2); + if (tagSearchField != null) { + String newText = tagSearchField.getText().toLowerCase(); + if (!tagSearch.equals(newText)) { + tagSearch = newText; + filterScroll.setList(getTagList()); + } + } + } + + // ==================== BUTTON ACTIONS ==================== @Override protected void actionPerformed(GuiButton guibutton) { int id = guibutton.id; - if (id == 0) { - close(); - } - if (id == 1) { - if (showingClones == 2) { - String sel = scroll.getSelected(); - if (sel == null) - return; - SpawnMobPacket.Server(posX, posY, posZ, sel, activeTab); - close(); - } else { - NBTTagCompound compound = getCompound(); - if (compound == null) - return; - SpawnMobPacket.Client(posX, posY, posZ, compound); - close(); - } - } - if (id == 2) { - if (showingClones == 2) { - String sel = scroll.getSelected(); - if (sel == null) - return; - MobSpawnerPacket.Server(posX, posY, posZ, sel, activeTab); - close(); - } else { - NBTTagCompound compound = getCompound(); - if (compound == null) - return; - MobSpawnerPacket.Client(posX, posY, posZ, compound); - close(); - } + // Tab side buttons (IDs 21-35) + if (id >= 21 && id <= 35) { + activeTab = id - 20; + activeFolder = null; + initGui(); + return; } + + // Mode buttons if (id == 3) { + savedContentScrollY = 0; showingClones = 0; initGui(); } if (id == 4) { + savedContentScrollY = 0; showingClones = 1; initGui(); } if (id == 5) { + savedContentScrollY = 0; showingClones = 2; + PacketClient.sendClient(new CloneFolderListPacket()); initGui(); } if (id == 16) { + savedContentScrollY = 0; showingClones = 3; initGui(); } if (id == 17) { close(); } + + // Open fullscreen GUI + if (id == 61) { + isFullscreen = true; + Minecraft.getMinecraft().displayGuiScreen( + new GuiNpcMobSpawnerFullscreen(posX, posY, posZ)); + } + + // Spawn + if (id == 1) { + if (showingClones == 2) { + String sel = scroll.getSelected(); + if (sel == null) return; + SpawnMobPacket.Server(posX, posY, posZ, sel, activeTab); + close(); + } else { + NBTTagCompound compound = getCompound(); + if (compound == null) return; + SpawnMobPacket.Client(posX, posY, posZ, compound); + close(); + } + } + + // Mob Spawner + if (id == 2) { + if (showingClones == 2) { + String sel = scroll.getSelected(); + if (sel == null) return; + MobSpawnerPacket.Server(posX, posY, posZ, sel, activeTab); + close(); + } else { + NBTTagCompound compound = getCompound(); + if (compound == null) return; + MobSpawnerPacket.Client(posX, posY, posZ, compound); + close(); + } + } + + // Remove if (id == 6) { if (scroll.getSelected() != null) { + int prevSelected = scroll.selected; if (showingClones == 2) { PacketClient.sendClient(new CloneRemovePacket(activeTab, scroll.getSelected())); return; } ClientCloneController.Instance.removeClone(scroll.getSelected(), activeTab); - scroll.selected = -1; initGui(); + if (scroll.list != null && !scroll.list.isEmpty()) { + scroll.selected = Math.min(prevSelected, scroll.list.size() - 1); + } else { + scroll.selected = -1; + } } } + + // Filter controls if (id == 11) { tagFilters = new HashSet<>(); filterScroll.setSelectedList(tagFilters); initGui(); } if (id == 12) { - GuiNpcButton button = (GuiNpcButton) guibutton; - displayTags = (byte) button.getValue(); + displayTags = (byte) ((GuiNpcButton) guibutton).getValue(); } if (id == 13) { - GuiNpcButton button = (GuiNpcButton) guibutton; - filterCondition = (byte) button.getValue(); + filterCondition = (byte) ((GuiNpcButton) guibutton).getValue(); } if (id == 14) { - GuiNpcButton button = (GuiNpcButton) guibutton; - ascending = (byte) button.getValue(); + ascending = (byte) ((GuiNpcButton) guibutton).getValue(); } if (id == 15) { - GuiNpcButton button = (GuiNpcButton) guibutton; - sortType = (byte) button.getValue(); - } - if (id > 20) { - activeTab = id - 20; - initGui(); + sortType = (byte) ((GuiNpcButton) guibutton).getValue(); } } + // ==================== DATA FROM SERVER ==================== @Override public void save() { - } @Override public void setGuiData(NBTTagCompound compound) { - if (compound.hasKey("CloneTags")) { + if (compound.hasKey("CloneFolders")) { + // Minimized doesn't use folders, but still handle the packet + initGui(); + } else if (compound.hasKey("CloneTags")) { this.tagMap = new TagMap(activeTab); NBTTagCompound cloneTags = compound.getCompoundTag("CloneTags"); this.tagMap.readNBT(cloneTags); @@ -521,29 +629,8 @@ public class GuiNpcMobSpawner extends GuiNPCInterface implements IGuiData { } this.list = list; - - this.rawList = new ArrayList(this.list); - if (this.tagMap != null) { - if (GuiNpcMobSpawner.displayTags == 0 || GuiNpcMobSpawner.displayTags == 1) { - for (int i = 0; i < this.list.size(); i++) { - StringBuilder npcName = new StringBuilder(list.get(i)); - if (this.tagMap.hasClone(list.get(i))) { - for (UUID tagUUID : this.tagMap.getUUIDsList(list.get(i))) { - Tag tag = tags.get(tagUUID); - if (tag != null) { - if (GuiNpcMobSpawner.displayTags == 1 || !tag.getIsHidden()) { - npcName.append(" [" + tag.name + "]"); - } - } - } - } - this.rawList.set(i, npcName.toString()); - } - } - } - + populateRawListWithTags(); scroll.setList(getSearchList(), ascending == 0, sortType == 0); } } - } diff --git a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerAdd.java b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerAdd.java index d2895fc44..5cea7fe28 100644 --- a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerAdd.java +++ b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerAdd.java @@ -2,6 +2,7 @@ package noppes.npcs.client.gui; import kamkeel.npcs.network.PacketClient; import kamkeel.npcs.network.packets.request.clone.CloneAllTagsShortPacket; +import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket; import kamkeel.npcs.network.packets.request.clone.ClonePreSavePacket; import kamkeel.npcs.network.packets.request.clone.CloneSavePacket; import net.minecraft.client.Minecraft; @@ -22,12 +23,14 @@ import noppes.npcs.client.gui.util.GuiNpcTextField; import noppes.npcs.client.gui.util.IGuiData; import noppes.npcs.client.gui.util.ISubGuiListener; import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.data.CloneFolder; import noppes.npcs.controllers.data.Tag; import noppes.npcs.entity.EntityNPCInterface; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.UUID; public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCallback, IGuiData, ISubGuiListener { @@ -35,9 +38,16 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall private Entity toClone; private NBTTagCompound compound; private static boolean serverSide = false; + private static int saveMode = 0; // 0=Tab, 1=Folder private static int tab = 1; + private static String folder = null; public boolean isNPC = false; + // Client-side folder names + private ArrayList clientFolderNames = new ArrayList<>(); + // Server-side folder names (populated via CloneFolderListPacket) + private static ArrayList serverFolderNames = new ArrayList<>(); + // Selected Tags to Add public static NBTTagList tagsCompound; public static HashSet addTags; @@ -68,6 +78,37 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall if (isNPC) { PacketClient.sendClient(new CloneAllTagsShortPacket()); } + + buildClientFolderNames(); + + // Request server folders if in server mode + if (serverSide) { + PacketClient.sendClient(new CloneFolderListPacket()); + } + + // Validate saved folder selection + validateFolder(); + } + + private void buildClientFolderNames() { + clientFolderNames.clear(); + if (ClientCloneController.Instance != null) { + for (CloneFolder f : ClientCloneController.Instance.getFolderList()) { + clientFolderNames.add(f.name); + } + } + } + + private List getActiveFolderList() { + return serverSide ? serverFolderNames : clientFolderNames; + } + + private void validateFolder() { + if (saveMode == 1 && folder != null) { + if (!getActiveFolderList().contains(folder)) { + folder = null; + } + } } @Override @@ -77,61 +118,138 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall addLabel(new GuiNpcLabel(0, "Save as", guiLeft + 4, guiTop + 6)); addTextField(new GuiNpcTextField(0, this, fontRendererObj, guiLeft + 4, guiTop + 18, 200, 20, name)); + // Tab/Folder toggle + addButton(new GuiNpcButton(6, guiLeft + 4, guiTop + 45, 50, 20, + new String[]{"Tab", "Folder"}, saveMode)); - addLabel(new GuiNpcLabel(1, "Tab", guiLeft + 10, guiTop + 50)); - addButton(new GuiButtonBiDirectional(2, guiLeft + 40, guiTop + 45, 60, 20, new String[]{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"}, tab - 1)); + // Destination selector based on saveMode + if (saveMode == 0) { + // Tab selector: 1-15 + String[] tabLabels = new String[15]; + for (int i = 0; i < 15; i++) tabLabels[i] = String.valueOf(i + 1); + int selectedTab = (tab >= 1 && tab <= 15) ? tab - 1 : 0; + addButton(new GuiButtonBiDirectional(2, guiLeft + 56, guiTop + 45, 90, 20, tabLabels, selectedTab)); + } else { + // Folder selector button — opens SubGuiFolderSelect + String btnText = folder != null ? folder : "Select a Folder"; + addButton(new GuiNpcButton(2, guiLeft + 56, guiTop + 45, 148, 20, btnText)); + } - addButton(new GuiNpcButton(3, guiLeft + 4, guiTop + 95, new String[]{"Client side", "Server side"}, serverSide ? 1 : 0)); - addLabel(new GuiNpcLabel(1, "Tab", guiLeft + 10, guiTop + 50)); - - addButton(new GuiNpcButton(0, guiLeft + 4, guiTop + 70, 80, 20, "gui.save")); + // Save / Cancel + GuiNpcButton saveBtn = new GuiNpcButton(0, guiLeft + 4, guiTop + 70, 80, 20, "gui.save"); + if (saveMode == 1 && folder == null) { + saveBtn.enabled = false; + } + addButton(saveBtn); addButton(new GuiNpcButton(1, guiLeft + 86, guiTop + 70, 80, 20, "gui.cancel")); + // Client / Server toggle + addButton(new GuiNpcButton(3, guiLeft + 4, guiTop + 95, new String[]{"Client side", "Server side"}, serverSide ? 1 : 0)); + if (isNPC) { addButton(new GuiNpcButton(4, guiLeft + 4, guiTop + 120, 99, 20, "cloner.wandTags")); addButton(new GuiNpcButton(5, guiLeft + 106, guiTop + 120, 99, 20, "cloner.npcTags")); if (addTags.size() > 0) { - addLabel(new GuiNpcLabel(6, "cloner.wandtagsapplied", guiLeft + 10, guiTop + 160)); + addLabel(new GuiNpcLabel(8, "cloner.wandtagsapplied", guiLeft + 10, guiTop + 160)); } } } + private void updateDestinationFromSelector() { + if (saveMode == 0) { + GuiNpcButton selector = getButton(2); + if (selector == null) return; + tab = selector.getValue() + 1; + folder = null; + } + // In folder mode, folder is already set via SubGuiFolderSelect + } + public void buttonEvent(GuiButton guibutton) { int id = guibutton.id; + + // Save if (id == 0) { + updateDestinationFromSelector(); String name = getTextField(0).getText(); if (name.isEmpty()) return; - int tab = ((GuiNpcButton) guibutton).getValue() + 1; + + if (saveMode == 1 && folder == null) + return; + if (!serverSide) { - if (ClientCloneController.Instance.getCloneData(null, name, tab) != null) + boolean exists; + if (saveMode == 1 && folder != null && ClientCloneController.Instance != null) { + exists = ClientCloneController.Instance.getCloneData(null, name, folder) != null; + } else { + exists = ClientCloneController.Instance.getCloneData(null, name, tab) != null; + } + if (exists) displayGuiScreen(new GuiYesNo(this, "Warning", "You are about to overwrite a clone", 1)); else confirmClicked(true, 0); - } else - PacketClient.sendClient(new ClonePreSavePacket(name, tab)); + } else { + if (saveMode == 1 && folder != null) { + PacketClient.sendClient(new ClonePreSavePacket(name, folder)); + } else { + PacketClient.sendClient(new ClonePreSavePacket(name, tab)); + } + } } + + // Cancel if (id == 1) { close(); } + + // Destination selector if (id == 2) { - tab = ((GuiNpcButton) guibutton).getValue() + 1; + if (saveMode == 0) { + // Tab mode — update from BiDirectional + updateDestinationFromSelector(); + } else { + // Folder mode — open folder selector Sub GUI + List folders = getActiveFolderList(); + setSubGui(new SubGuiFolderSelect(folders, folder)); + } } + + // Client / Server toggle if (id == 3) { + boolean wasServerSide = serverSide; serverSide = ((GuiNpcButton) guibutton).getValue() == 1; + + if (serverSide && !wasServerSide) { + // Switching to server mode — request fresh folder list + PacketClient.sendClient(new CloneFolderListPacket()); + } + + // Validate folder exists on the new side + validateFolder(); + initGui(); } + + // Wand Tags if (id == 4) { if (isNPC) { this.setSubGui(new SubGuiClonerQuickTags(this)); } } + + // NPC Tags if (id == 5) { if (isNPC) { this.setSubGui(new SubGuiClonerNPCTags((EntityNPCInterface) toClone, this)); } } - } + // Tab/Folder mode toggle + if (id == 6) { + saveMode = ((GuiNpcButton) guibutton).getValue(); + initGui(); + } + } @Override public void confirmClicked(boolean confirm, int id) { @@ -145,13 +263,21 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall if (isNPC) { compound.setTag("TagUUIDs", tagsCompound); } - ClientCloneController.Instance.addClone(compound, name, tab, extraTags); + if (saveMode == 1 && folder != null && ClientCloneController.Instance != null) { + ClientCloneController.Instance.addClone(compound, name, folder, extraTags); + } else { + ClientCloneController.Instance.addClone(compound, name, tab, extraTags); + } } else { NBTTagCompound compounder = new NBTTagCompound(); if (isNPC) { compounder.setTag("TagUUIDs", tagsCompound); } - PacketClient.sendClient(new CloneSavePacket(name, tab, extraTags, compounder)); + if (saveMode == 1 && folder != null) { + PacketClient.sendClient(new CloneSavePacket(name, folder, extraTags, compounder)); + } else { + PacketClient.sendClient(new CloneSavePacket(name, tab, extraTags, compounder)); + } } close(); @@ -159,7 +285,6 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall displayGuiScreen(this); } - @Override public void save() { } @@ -171,6 +296,17 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall displayGuiScreen(new GuiYesNo(this, "Warning", "You are about to overwrite a clone", 1)); else confirmClicked(true, 0); + } else if (compound.hasKey("CloneFolders")) { + // Server folder list arrived + NBTTagList folderList = compound.getTagList("CloneFolders", 10); + serverFolderNames.clear(); + for (int i = 0; i < folderList.tagCount(); i++) { + CloneFolder f = new CloneFolder(); + f.readNBT(folderList.getCompoundTagAt(i)); + serverFolderNames.add(f.name); + } + validateFolder(); + initGui(); } else if (compound.hasKey("ShortTags")) { NBTTagList validTags = compound.getTagList("ShortTags", 10); tagMap.clear(); @@ -193,6 +329,12 @@ public class GuiNpcMobSpawnerAdd extends GuiNPCInterface implements GuiYesNoCall @Override public void subGuiClosed(SubGuiInterface subgui) { + if (subgui instanceof SubGuiFolderSelect) { + SubGuiFolderSelect sel = (SubGuiFolderSelect) subgui; + if (sel.selectedFolder != null) { + folder = sel.selectedFolder; + } + } initGui(); } diff --git a/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java new file mode 100644 index 000000000..4919ccbae --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/GuiNpcMobSpawnerFullscreen.java @@ -0,0 +1,1045 @@ +package noppes.npcs.client.gui; + +import kamkeel.npcs.network.PacketClient; +import kamkeel.npcs.network.packets.request.clone.CloneAllTagsPacket; +import kamkeel.npcs.network.packets.request.clone.CloneFolderCrudPacket; +import kamkeel.npcs.network.packets.request.clone.CloneFolderListPacket; +import kamkeel.npcs.network.packets.request.clone.CloneListPacket; +import kamkeel.npcs.network.packets.request.clone.CloneMovePacket; +import kamkeel.npcs.network.packets.request.clone.CloneRemovePacket; +import kamkeel.npcs.network.packets.request.clone.CloneTagListPacket; +import kamkeel.npcs.network.packets.request.clone.MobSpawnerPacket; +import kamkeel.npcs.network.packets.request.clone.SpawnMobPacket; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiButton; +import net.minecraft.entity.Entity; +import net.minecraft.entity.EntityList; +import net.minecraft.entity.EntityLiving; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.util.StatCollector; +import net.minecraft.world.World; +import noppes.npcs.client.controllers.ClientCloneController; +import noppes.npcs.client.controllers.ClientTagMapController; +import noppes.npcs.client.gui.util.GuiCustomScroll; +import noppes.npcs.client.gui.util.GuiCustomScrollCloner; +import noppes.npcs.client.gui.util.GuiCustomScrollIcons; +import noppes.npcs.client.gui.util.GuiDirectory; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.IClonerGui; +import noppes.npcs.client.gui.util.IGuiData; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.data.CloneFolder; +import noppes.npcs.controllers.data.Tag; +import noppes.npcs.controllers.data.TagMap; + +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class GuiNpcMobSpawnerFullscreen extends GuiDirectory implements IGuiData, IClonerGui { + + // ==================== INSTANCE STATE ==================== + public TagMap tagMap; + private final GuiCustomScrollCloner scroll = new GuiCustomScrollCloner(this, 0); + private final GuiCustomScroll filterScroll = new GuiCustomScroll(this, 1); + private final GuiCustomScrollIcons navScroll = new GuiCustomScrollIcons(this, 2); + + private int posX, posY, posZ; + private List list; + private List rawList; + private List tagList; + private List folderNames = new ArrayList<>(); + + // Left nav list data + private List navNames = new ArrayList<>(); + private List navIcons = new ArrayList<>(); + private String navSearch = ""; + + // Move workflow state + private int movePhase = 0; // 0=off, 1=selecting clones, 2=selecting destination + private HashSet moveSelection = new HashSet<>(); + + // ==================== CONSTRUCTOR ==================== + public GuiNpcMobSpawnerFullscreen(int posX, int posY, int posZ) { + super(); + this.posX = posX; + this.posY = posY; + this.posZ = posZ; + + // Configure GuiDirectory layout percentages + leftPanelPercent = 0.15f; + rightPanelPercent = 0.08f; + minLeftPanelW = 120; + minRightPanelW = 62; + + PacketClient.sendClient(new CloneAllTagsPacket()); + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneFolderListPacket()); + } + loadFolderNames(); + } + + private void loadFolderNames() { + folderNames.clear(); + if (ClientCloneController.Instance != null) { + for (CloneFolder folder : ClientCloneController.Instance.getFolderList()) { + folderNames.add(folder.name); + } + } + } + + // ==================== IClonerGui ==================== + @Override + public int getShowingClones() { + return GuiNpcMobSpawner.showingClones; + } + + @Override + public HashMap getTags() { + return GuiNpcMobSpawner.tags; + } + + @Override + public TagMap getTagMap() { + return tagMap; + } + + // ==================== GuiDirectory HOOKS ==================== + @Override + public void initGui() { + // Save scroll positions before super.initGui() clears everything + GuiNpcMobSpawner.savedContentScrollY = scroll.scrollY; + GuiNpcMobSpawner.savedNavScrollY = navScroll.scrollY; + + super.initGui(); + } + + @Override + protected void drawPanels() { + if (GuiNpcMobSpawner.showingClones < 3) { + super.drawPanels(); + } else { + // Filters page: only draw top bar, no side panels + } + } + + @Override + protected void initTopBar(int topBtnY) { + int topBtnW = 55; + int x = originX + 2; + + GuiNpcButton clonesBtn = new GuiNpcButton(3, x, topBtnY, topBtnW, btnH, "spawner.clones"); + clonesBtn.enabled = GuiNpcMobSpawner.showingClones != 0; + addButton(clonesBtn); + x += topBtnW + 2; + + GuiNpcButton serverBtn = new GuiNpcButton(5, x, topBtnY, topBtnW, btnH, "gui.server"); + serverBtn.enabled = GuiNpcMobSpawner.showingClones != 2; + addButton(serverBtn); + x += topBtnW + 2; + + GuiNpcButton entitiesBtn = new GuiNpcButton(4, x, topBtnY, topBtnW, btnH, "spawner.entities"); + entitiesBtn.enabled = GuiNpcMobSpawner.showingClones != 1; + addButton(entitiesBtn); + + // Top bar right side + int closeX = originX + usableW - btnH - 2; + addButton(new GuiNpcButton(17, closeX, topBtnY, btnH, btnH, "X")); + int minimizeX = closeX - btnH - 2; + addButton(new GuiNpcButton(61, minimizeX, topBtnY, btnH, btnH, "-")); + int filterBtnW = 50; + int filterX = minimizeX - filterBtnW - 2; + GuiNpcButton filterBtn = new GuiNpcButton(16, filterX, topBtnY, filterBtnW, btnH, "gui.filters"); + filterBtn.enabled = GuiNpcMobSpawner.showingClones != 3; + addButton(filterBtn); + + // Clone search field in top bar (only when not on filters page) + if (GuiNpcMobSpawner.showingClones < 3) { + int searchX = x + topBtnW + 6; + int searchW = filterX - searchX - 4; + if (searchW > 20) { + addTextField(new GuiNpcTextField(1, this, fontRendererObj, + searchX, topBtnY, searchW, btnH, GuiNpcMobSpawner.search)); + } + } + + // Disable mode buttons during move + if (movePhase > 0) { + clonesBtn.enabled = false; + serverBtn.enabled = false; + entitiesBtn.enabled = false; + filterBtn.enabled = false; + } + } + + @Override + protected void initLeftPanel() { + if (GuiNpcMobSpawner.showingClones >= 3) return; + + boolean folderSelected = GuiNpcMobSpawner.activeFolder != null; + int bottomRows = folderSelected ? 3 : 2; + int bottomH = bottomRows * (btnH + gap); + int navH = contentH - bottomH; + buildNavList(); + + int savedNavScroll = GuiNpcMobSpawner.savedNavScrollY; + navScroll.clear(); + navScroll.colors.clear(); + navScroll.setSize(leftPanelW, navH); + navScroll.guiLeft = originX; + navScroll.guiTop = contentY; + navScroll.setListWithIcons(navNames, navIcons); + navScroll.setSelected(getNavSelection()); + navScroll.scrollY = Math.min(savedNavScroll, Math.max(0, navScroll.maxScrollY)); + + // Phase 2: color nav items orange (selectable) or red (current location, not selectable) + if (movePhase == 2) { + String currentNav = getNavSelection(); + for (String name : navNames) { + navScroll.colors.put(name, name.equals(currentNav) ? 0xFF5555 : 0xFFAA00); + } + } + + addScroll(navScroll); + + // Nav search field below the scroll + int navSearchY = contentY + navH + gap; + addTextField(new GuiNpcTextField(3, this, fontRendererObj, + originX, navSearchY, leftPanelW, btnH, navSearch)); + + // Add folder button + int addFolderY = navSearchY + btnH + gap; + GuiNpcButton addFolderBtn = new GuiNpcButton(40, originX, addFolderY, leftPanelW, btnH, "+ Add Folder"); + if (movePhase > 0) addFolderBtn.enabled = false; + addButton(addFolderBtn); + + // Rename/Delete folder buttons (only when a folder is selected) + if (folderSelected) { + int folderCrudY = addFolderY + btnH + gap; + int halfW = (leftPanelW - gap) / 2; + GuiNpcButton renameBtn = new GuiNpcButton(41, originX, folderCrudY, halfW, btnH, "Rename"); + if (movePhase > 0) renameBtn.enabled = false; + addButton(renameBtn); + GuiNpcButton delBtn = new GuiNpcButton(42, originX + halfW + gap, folderCrudY, halfW, btnH, "gui.remove"); + if (ClientCloneController.Instance != null) { + List clones = ClientCloneController.Instance.getClones(GuiNpcMobSpawner.activeFolder); + delBtn.enabled = clones == null || clones.isEmpty(); + } + if (movePhase > 0) delBtn.enabled = false; + addButton(delBtn); + } + } + + @Override + protected void initCenterPanel() { + if (GuiNpcMobSpawner.showingClones >= 3) { + showFiltersPage(); + return; + } + + int savedScroll = GuiNpcMobSpawner.savedContentScrollY; + scroll.clear(); + scroll.setSize(contentW, contentH); + scroll.guiLeft = contentX; + scroll.guiTop = contentY; + addScroll(scroll); + + if (GuiNpcMobSpawner.showingClones == 0 || GuiNpcMobSpawner.showingClones == 2) { + // Always clear colors — only phase 2 re-populates them + scroll.colors.clear(); + + // Multi-select mode during move phase 1 + if (movePhase == 1) { + scroll.multipleSelection = true; + scroll.setSelectable(true); + scroll.setSelectedList(moveSelection); + } else if (movePhase == 2) { + // Phase 2: show selection as yellow, freeze scroll + scroll.multipleSelection = true; + scroll.setSelectedList(moveSelection); + scroll.setSelectable(false); + } else { + scroll.multipleSelection = false; + scroll.setSelectable(true); + } + + showClones(); + + // Phase 2: color selected clones yellow + if (movePhase == 2) { + for (String name : moveSelection) { + scroll.colors.put(name, 0xFFFF55); + } + } + + // Restore scroll position after list is populated + scroll.scrollY = Math.min(savedScroll, Math.max(0, scroll.maxScrollY)); + } else { + showEntities(); + scroll.scrollY = Math.min(savedScroll, Math.max(0, scroll.maxScrollY)); + } + } + + @Override + protected void initRightPanel(int startY) { + if (GuiNpcMobSpawner.showingClones >= 3) return; + + int btnGap = 4; + int panelH = contentH - 4; + int midY = startY + (panelH / 2); + int bottomY = contentY + contentH - btnH; + + // Spawn — top + GuiNpcButton spawnBtn = new GuiNpcButton(1, rightX, startY, rightPanelW, btnH, "item.monsterPlacer.name"); + if (movePhase > 0) spawnBtn.enabled = false; + addButton(spawnBtn); + + // Spawner — below midpoint + GuiNpcButton spawnerBtn = new GuiNpcButton(2, rightX, midY + btnGap / 2, rightPanelW, btnH, "spawner.mobspawner"); + if (movePhase > 0) spawnerBtn.enabled = false; + addButton(spawnerBtn); + + if (GuiNpcMobSpawner.showingClones == 0 || GuiNpcMobSpawner.showingClones == 2) { + // Move — above midpoint + String moveLabel = movePhase > 0 ? "Moving" : "Move"; + GuiNpcButton moveBtn = new GuiNpcButton(50, rightX, midY - btnH - btnGap / 2, rightPanelW, btnH, moveLabel); + addButton(moveBtn); + + // Confirm — below Move (only visible during phase 1) + if (movePhase == 1) { + GuiNpcButton confirmBtn = new GuiNpcButton(51, rightX, midY - btnH - btnGap / 2 + btnH + btnGap, rightPanelW, btnH, "Confirm"); + confirmBtn.enabled = !moveSelection.isEmpty(); + addButton(confirmBtn); + } + + // Remove — bottom + GuiNpcButton removeBtn = new GuiNpcButton(6, rightX, bottomY, rightPanelW, btnH, "gui.remove"); + if (movePhase > 0) removeBtn.enabled = false; + addButton(removeBtn); + } + } + + // ==================== OVERLAY DRAWING ==================== + @Override + protected void drawOverlay(int mouseX, int mouseY, float partialTicks) { + if (movePhase == 1) { + String text = "Select Clones to Move"; + int textW = fontRendererObj.getStringWidth(text); + fontRendererObj.drawStringWithShadow(text, width / 2 - textW / 2, height - pad - 10, 0x55FF55); + } else if (movePhase == 2) { + String text = "Select a Tab or Folder"; + int textW = fontRendererObj.getStringWidth(text); + fontRendererObj.drawStringWithShadow(text, width / 2 - textW / 2, height - pad - 10, 0xFF5555); + } + } + + // ==================== LEFT NAVIGATION ==================== + private void buildNavList() { + navNames.clear(); + navIcons.clear(); + + String searchLower = navSearch.toLowerCase(); + + for (int i = 1; i <= 15; i++) { + String name = "Tab " + i; + if (searchLower.isEmpty() || name.toLowerCase().contains(searchLower)) { + navNames.add(name); + navIcons.add(GuiCustomScrollIcons.ICON_TAB); + } + } + for (String folder : folderNames) { + if (searchLower.isEmpty() || folder.toLowerCase().contains(searchLower)) { + navNames.add(folder); + navIcons.add(GuiCustomScrollIcons.ICON_FOLDER); + } + } + } + + private String getNavSelection() { + if (GuiNpcMobSpawner.activeFolder != null) return GuiNpcMobSpawner.activeFolder; + if (GuiNpcMobSpawner.activeTab >= 1 && GuiNpcMobSpawner.activeTab <= 15) { + return "Tab " + GuiNpcMobSpawner.activeTab; + } + return "Tab 1"; + } + + private void handleNavClick(String selected) { + if (selected == null) return; + + if (selected.startsWith("Tab ")) { + try { + int tabNum = Integer.parseInt(selected.substring(4)); + if (tabNum >= 1 && tabNum <= 15) { + GuiNpcMobSpawner.activeTab = tabNum; + GuiNpcMobSpawner.activeFolder = null; + initGui(); + return; + } + } catch (NumberFormatException ignored) { + } + } + + GuiNpcMobSpawner.activeFolder = selected; + GuiNpcMobSpawner.activeTab = -1; + initGui(); + } + + // ==================== FILTERS PAGE ==================== + private void showFiltersPage() { + int scrollW = Math.min(usableW / 2 - gap * 2, 240); + int scrollX = originX + gap; + int scrollTopY = contentY + 16; + int scrollH = contentH - 16 - btnH - gap * 2; + + addLabel(new GuiNpcLabel(1, StatCollector.translateToLocal("cloner.tagFilters"), + scrollX + 2, contentY + 4, 0xFFFFFF)); + + filterScroll.clear(); + filterScroll.setSize(scrollW, scrollH); + filterScroll.guiLeft = scrollX; + filterScroll.guiTop = scrollTopY; + filterScroll.multipleSelection = true; + filterScroll.setSelectedList(GuiNpcMobSpawner.tagFilters); + addScroll(filterScroll); + + addTextField(new GuiNpcTextField(2, this, fontRendererObj, + scrollX, scrollTopY + scrollH + gap, scrollW, btnH, GuiNpcMobSpawner.tagSearch)); + + // Settings column + int col2X = scrollX + scrollW + gap * 4; + int controlW = 90; + int controlX = col2X + 80; + int rowH = 28; + int rowY = scrollTopY; + + addLabel(new GuiNpcLabel(2, StatCollector.translateToLocal("cloner.tagVisibility"), + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(12, controlX, rowY, controlW, btnH, + new String[]{"display.show", "display.all", "display.hide"}, GuiNpcMobSpawner.displayTags)); + + rowY += rowH; + addLabel(new GuiNpcLabel(3, StatCollector.translateToLocal("filter.contains"), + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(13, controlX, rowY, controlW, btnH, + new String[]{"filter.any", "filter.all", "filter.notany", "filter.notall"}, GuiNpcMobSpawner.filterCondition)); + + rowY += rowH + 4; + addButton(new GuiNpcButton(11, col2X, rowY, controlW + 80, btnH, "gui.deselectAll")); + + rowY += rowH + 12; + addLabel(new GuiNpcLabel(4, StatCollector.translateToLocal("cloner.order"), + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(14, controlX, rowY, controlW, btnH, + new String[]{"cloner.ascending", "cloner.descending"}, GuiNpcMobSpawner.ascending)); + + rowY += rowH; + addLabel(new GuiNpcLabel(5, StatCollector.translateToLocal("cloner.type"), + col2X, rowY + 5, 0xFFFFFF)); + addButton(new GuiNpcButton(15, controlX, rowY, controlW, btnH, + new String[]{"cloner.name", "cloner.date"}, GuiNpcMobSpawner.sortType)); + + filterScroll.setList(getTagList()); + } + + // ==================== CLONE / ENTITY DISPLAY ==================== + private void showClones() { + if (GuiNpcMobSpawner.activeFolder != null) { + showFolderClones(); + return; + } + + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneTagListPacket(GuiNpcMobSpawner.activeTab)); + PacketClient.sendClient(new CloneListPacket(GuiNpcMobSpawner.activeTab)); + return; + } + + if (GuiNpcMobSpawner.sortType == 0) { + this.list = ClientCloneController.Instance.getClones(GuiNpcMobSpawner.activeTab); + } else { + this.list = ClientCloneController.Instance.getClonesDate(GuiNpcMobSpawner.activeTab); + } + + this.tagMap = ClientTagMapController.Instance.getTagMap(GuiNpcMobSpawner.activeTab); + populateRawListWithTags(); + scroll.setList(getSearchList(), GuiNpcMobSpawner.ascending == 0, GuiNpcMobSpawner.sortType == 0); + } + + private void showFolderClones() { + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneTagListPacket(GuiNpcMobSpawner.activeFolder)); + PacketClient.sendClient(new CloneListPacket(GuiNpcMobSpawner.activeFolder)); + return; + } + + if (ClientCloneController.Instance != null) { + if (GuiNpcMobSpawner.sortType == 0) { + this.list = ClientCloneController.Instance.getClones(GuiNpcMobSpawner.activeFolder); + } else { + this.list = ClientCloneController.Instance.getClonesDate(GuiNpcMobSpawner.activeFolder); + } + this.tagMap = ClientTagMapController.Instance.getTagMap(GuiNpcMobSpawner.activeFolder); + } else { + this.list = new ArrayList<>(); + this.tagMap = null; + } + + populateRawListWithTags(); + scroll.setList(getSearchList(), GuiNpcMobSpawner.ascending == 0, GuiNpcMobSpawner.sortType == 0); + } + + private void showEntities() { + Map data = EntityList.stringToClassMapping; + ArrayList list = new ArrayList(); + for (Object name : data.keySet()) { + Class c = (Class) data.get(name); + try { + if (EntityLiving.class.isAssignableFrom(c) + && c.getConstructor(new Class[]{World.class}) != null + && !Modifier.isAbstract(c.getModifiers())) + list.add(name.toString()); + } catch (SecurityException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e) { + } + } + this.list = list; + this.rawList = new ArrayList(this.list); + scroll.setList(getSearchList(), GuiNpcMobSpawner.ascending == 0, GuiNpcMobSpawner.sortType == 0); + } + + private void populateRawListWithTags() { + this.rawList = new ArrayList<>(this.list); + if (this.tagMap != null) { + if (GuiNpcMobSpawner.displayTags == 0 || GuiNpcMobSpawner.displayTags == 1) { + for (int i = 0; i < this.list.size(); i++) { + StringBuilder npcName = new StringBuilder(list.get(i)); + if (this.tagMap.hasClone(list.get(i))) { + for (UUID tagUUID : this.tagMap.getUUIDsList(list.get(i))) { + Tag tag = GuiNpcMobSpawner.tags.get(tagUUID); + if (tag != null) { + if (GuiNpcMobSpawner.displayTags == 1 || !tag.getIsHidden()) { + npcName.append(" [" + tag.name + "]"); + } + } + } + } + this.rawList.set(i, npcName.toString()); + } + } + } + } + + private NBTTagCompound getCompound() { + String sel = scroll.getSelected(); + if (sel == null) + return null; + + if (GuiNpcMobSpawner.showingClones == 0) { + if (GuiNpcMobSpawner.activeFolder != null && ClientCloneController.Instance != null) { + return ClientCloneController.Instance.getCloneData(player, sel, GuiNpcMobSpawner.activeFolder); + } + return ClientCloneController.Instance.getCloneData(player, sel, GuiNpcMobSpawner.activeTab); + } else { + Entity entity = EntityList.createEntityByName(sel, Minecraft.getMinecraft().theWorld); + if (entity == null) + return null; + NBTTagCompound compound = new NBTTagCompound(); + entity.writeToNBTOptional(compound); + return compound; + } + } + + // ==================== SEARCH / FILTER ==================== + private List getSearchList() { + if (this.list == null) { + this.list = new ArrayList(); + } + if (this.rawList == null) { + this.rawList = new ArrayList(); + } + + if (GuiNpcMobSpawner.tagFilters.size() == 0 || GuiNpcMobSpawner.showingClones == 1) { + if (GuiNpcMobSpawner.search.isEmpty()) + return new ArrayList(this.list); + else { + List list = new ArrayList(); + for (int i = 0; i < this.list.size(); i++) { + if (this.rawList.get(i).toLowerCase().contains(GuiNpcMobSpawner.search)) { + list.add(this.list.get(i)); + } + } + return list; + } + } + + if (GuiNpcMobSpawner.search.isEmpty()) { + List list = new ArrayList(); + for (String name : this.list) { + if (tagMap.hasClone(name)) { + if (meetsCondition(name)) { + list.add(name); + } + } + } + return list; + } + + List list = new ArrayList(); + for (int i = 0; i < this.list.size(); i++) { + if (this.rawList.get(i).toLowerCase().contains(GuiNpcMobSpawner.search)) { + String npcName = this.list.get(i); + if (tagMap.hasClone(npcName)) { + if (meetsCondition(npcName)) { + list.add(npcName); + } + } + } + } + return list; + } + + private boolean meetsCondition(String name) { + if (tagMap == null) { + return true; + } + boolean conditionMet = true; + boolean allRequirement = GuiNpcMobSpawner.filterCondition == 1 || GuiNpcMobSpawner.filterCondition == 3; + + for (String tagName : GuiNpcMobSpawner.tagFilters) { + UUID tagUUID = GuiNpcMobSpawner.tagNames.get(tagName); + if (tagUUID != null) { + boolean hasTag = tagMap.hasTag(name, tagUUID); + if (!allRequirement) { + if (!hasTag && GuiNpcMobSpawner.filterCondition == 0) { + conditionMet = false; + break; + } else if (hasTag && GuiNpcMobSpawner.filterCondition == 2) { + conditionMet = false; + break; + } + } else { + if (!hasTag && GuiNpcMobSpawner.filterCondition == 1) { + conditionMet = false; + break; + } else if (hasTag && GuiNpcMobSpawner.filterCondition == 3) { + conditionMet = false; + break; + } + } + } + } + return conditionMet; + } + + private List getTagList() { + if (this.tagList == null) { + this.tagList = new ArrayList(); + } + if (GuiNpcMobSpawner.tagSearch.isEmpty()) + return new ArrayList(GuiNpcMobSpawner.tagNames.keySet()); + + List list = new ArrayList(); + for (String name : this.tagList) { + if (name.toLowerCase().contains(GuiNpcMobSpawner.tagSearch)) + list.add(name); + } + return list; + } + + // ==================== SCROLL EVENTS ==================== + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + // Phase 2: ignore clicks on the center clone scroll + if (guiCustomScroll.id == 0 && movePhase == 2) { + return; + } + + if (guiCustomScroll.id == 0 && movePhase == 1) { + moveSelection = scroll.getSelectedList(); + GuiNpcButton confirmBtn = getButton(51); + if (confirmBtn != null) { + confirmBtn.enabled = !moveSelection.isEmpty(); + } + return; + } + + if (guiCustomScroll.id == 2) { + String selected = navScroll.getSelected(); + if (selected == null) return; + + if (movePhase == 2) { + // Cannot move to the same location + if (selected.equals(getNavSelection())) { + return; + } + // Selecting destination from nav + if (selected.startsWith("Tab ")) { + try { + int tabNum = Integer.parseInt(selected.substring(4)); + setSubGui(new SubGuiMoveConfirm(moveSelection.size(), tabNum, null)); + } catch (NumberFormatException ignored) { + } + } else { + setSubGui(new SubGuiMoveConfirm(moveSelection.size(), -1, selected)); + } + return; + } + + if (movePhase == 1) { + movePhase = 0; + moveSelection.clear(); + } + + handleNavClick(selected); + } + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) { + } + + // ==================== KEYBOARD ==================== + @Override + public void keyTyped(char c, int i) { + super.keyTyped(c, i); + + GuiNpcTextField searchField = getTextField(1); + if (searchField != null) { + String newText = searchField.getText().toLowerCase(); + if (!GuiNpcMobSpawner.search.equals(newText)) { + GuiNpcMobSpawner.search = newText; + scroll.setList(getSearchList(), GuiNpcMobSpawner.ascending == 0, GuiNpcMobSpawner.sortType == 0); + } + } + + GuiNpcTextField tagSearchField = getTextField(2); + if (tagSearchField != null) { + String newText = tagSearchField.getText().toLowerCase(); + if (!GuiNpcMobSpawner.tagSearch.equals(newText)) { + GuiNpcMobSpawner.tagSearch = newText; + filterScroll.setList(getTagList()); + } + } + + GuiNpcTextField navSearchField = getTextField(3); + if (navSearchField != null) { + String newNavSearch = navSearchField.getText().toLowerCase(); + if (!navSearch.equals(newNavSearch)) { + navSearch = newNavSearch; + buildNavList(); + navScroll.setListWithIcons(navNames, navIcons); + navScroll.setSelected(getNavSelection()); + } + } + } + + // ==================== BUTTON ACTIONS ==================== + @Override + protected void actionPerformed(GuiButton guibutton) { + int id = guibutton.id; + + if (id == 3) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; + GuiNpcMobSpawner.showingClones = 0; + loadFolderNames(); + initGui(); + } + if (id == 4) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; + GuiNpcMobSpawner.showingClones = 1; + initGui(); + } + if (id == 5) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; + GuiNpcMobSpawner.showingClones = 2; + PacketClient.sendClient(new CloneFolderListPacket()); + initGui(); + } + if (id == 16) { + movePhase = 0; + moveSelection.clear(); + GuiNpcMobSpawner.savedContentScrollY = 0; + GuiNpcMobSpawner.savedNavScrollY = 0; + GuiNpcMobSpawner.showingClones = 3; + initGui(); + } + if (id == 17) { + close(); + } + + // Minimize — return to normal GUI + if (id == 61) { + GuiNpcMobSpawner.isFullscreen = false; + Minecraft.getMinecraft().displayGuiScreen( + new GuiNpcMobSpawner(posX, posY, posZ)); + } + + // Spawn + if (id == 1) { + if (GuiNpcMobSpawner.showingClones == 2) { + String sel = scroll.getSelected(); + if (sel == null) return; + if (GuiNpcMobSpawner.activeFolder != null) + SpawnMobPacket.ServerFolder(posX, posY, posZ, sel, GuiNpcMobSpawner.activeFolder); + else + SpawnMobPacket.Server(posX, posY, posZ, sel, GuiNpcMobSpawner.activeTab); + close(); + } else { + NBTTagCompound compound = getCompound(); + if (compound == null) return; + SpawnMobPacket.Client(posX, posY, posZ, compound); + close(); + } + } + + // Mob Spawner + if (id == 2) { + if (GuiNpcMobSpawner.showingClones == 2) { + String sel = scroll.getSelected(); + if (sel == null) return; + if (GuiNpcMobSpawner.activeFolder != null) + MobSpawnerPacket.ServerFolder(posX, posY, posZ, sel, GuiNpcMobSpawner.activeFolder); + else + MobSpawnerPacket.Server(posX, posY, posZ, sel, GuiNpcMobSpawner.activeTab); + close(); + } else { + NBTTagCompound compound = getCompound(); + if (compound == null) return; + MobSpawnerPacket.Client(posX, posY, posZ, compound); + close(); + } + } + + // Remove + if (id == 6) { + if (scroll.getSelected() != null) { + int prevSelected = scroll.selected; + if (GuiNpcMobSpawner.showingClones == 2) { + if (GuiNpcMobSpawner.activeFolder != null) + PacketClient.sendClient(new CloneRemovePacket(GuiNpcMobSpawner.activeFolder, scroll.getSelected())); + else + PacketClient.sendClient(new CloneRemovePacket(GuiNpcMobSpawner.activeTab, scroll.getSelected())); + return; + } + if (GuiNpcMobSpawner.activeFolder != null && ClientCloneController.Instance != null) + ClientCloneController.Instance.removeClone(scroll.getSelected(), GuiNpcMobSpawner.activeFolder); + else + ClientCloneController.Instance.removeClone(scroll.getSelected(), GuiNpcMobSpawner.activeTab); + initGui(); + if (scroll.list != null && !scroll.list.isEmpty()) { + scroll.selected = Math.min(prevSelected, scroll.list.size() - 1); + } else { + scroll.selected = -1; + } + } + } + + // Move toggle + if (id == 50) { + if (movePhase == 0) { + movePhase = 1; + moveSelection.clear(); + } else { + movePhase = 0; + moveSelection.clear(); + } + initGui(); + } + // Confirm move selection + if (id == 51) { + if (movePhase == 1 && !moveSelection.isEmpty()) { + movePhase = 2; + initGui(); + } + } + + // Filter controls + if (id == 11) { + GuiNpcMobSpawner.tagFilters = new HashSet<>(); + filterScroll.setSelectedList(GuiNpcMobSpawner.tagFilters); + initGui(); + } + if (id == 12) { + GuiNpcMobSpawner.displayTags = (byte) ((GuiNpcButton) guibutton).getValue(); + } + if (id == 13) { + GuiNpcMobSpawner.filterCondition = (byte) ((GuiNpcButton) guibutton).getValue(); + } + if (id == 14) { + GuiNpcMobSpawner.ascending = (byte) ((GuiNpcButton) guibutton).getValue(); + } + if (id == 15) { + GuiNpcMobSpawner.sortType = (byte) ((GuiNpcButton) guibutton).getValue(); + } + + // Create folder + if (id == 40) { + setSubGui(new SubGuiCloneFolderName("")); + } + // Rename folder + if (id == 41) { + if (GuiNpcMobSpawner.activeFolder != null) + setSubGui(new SubGuiCloneFolderName(GuiNpcMobSpawner.activeFolder)); + } + // Delete folder + if (id == 42) { + if (GuiNpcMobSpawner.activeFolder != null) { + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneFolderCrudPacket( + CloneFolderCrudPacket.ACTION_DELETE, GuiNpcMobSpawner.activeFolder)); + } else if (ClientCloneController.Instance != null) { + ClientCloneController.Instance.deleteFolder(GuiNpcMobSpawner.activeFolder); + loadFolderNames(); + } + GuiNpcMobSpawner.activeFolder = null; + GuiNpcMobSpawner.activeTab = 1; + initGui(); + } + } + } + + // ==================== SUB GUI ==================== + @Override + public void subGuiClosed(SubGuiInterface subgui) { + if (subgui instanceof SubGuiCloneFolderName) { + SubGuiCloneFolderName folderGui = (SubGuiCloneFolderName) subgui; + if (folderGui.cancelled) return; + + String newName = folderGui.getFolderName(); + if (newName == null || newName.isEmpty()) return; + + if (folderGui.isRename()) { + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneFolderCrudPacket( + CloneFolderCrudPacket.ACTION_RENAME, folderGui.getOriginalName(), newName)); + } else if (ClientCloneController.Instance != null) { + ClientCloneController.Instance.renameFolder(folderGui.getOriginalName(), newName); + loadFolderNames(); + } + GuiNpcMobSpawner.activeFolder = newName; + GuiNpcMobSpawner.activeTab = -1; + } else { + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneFolderCrudPacket( + CloneFolderCrudPacket.ACTION_CREATE, newName)); + } else if (ClientCloneController.Instance != null) { + ClientCloneController.Instance.createFolder(newName); + loadFolderNames(); + } + GuiNpcMobSpawner.activeFolder = newName; + GuiNpcMobSpawner.activeTab = -1; + } + initGui(); + } + + if (subgui instanceof SubGuiMoveConfirm) { + SubGuiMoveConfirm confirm = (SubGuiMoveConfirm) subgui; + if (confirm.confirmed && !moveSelection.isEmpty()) { + int fromTab = GuiNpcMobSpawner.activeFolder != null ? -1 : GuiNpcMobSpawner.activeTab; + String fromFolder = GuiNpcMobSpawner.activeFolder; + int toTab = confirm.destTab; + String toFolder = confirm.destFolder; + + for (String cloneName : moveSelection) { + if (GuiNpcMobSpawner.showingClones == 2) { + PacketClient.sendClient(new CloneMovePacket( + cloneName, fromTab, fromFolder, toTab, toFolder)); + } else if (ClientCloneController.Instance != null) { + if (fromFolder != null && toFolder != null) { + ClientCloneController.Instance.moveClone(cloneName, fromFolder, toFolder); + } else if (fromFolder != null) { + ClientCloneController.Instance.moveClone(cloneName, fromFolder, toTab); + } else if (toFolder != null) { + ClientCloneController.Instance.moveClone(cloneName, fromTab, toFolder); + } else { + ClientCloneController.Instance.moveClone(cloneName, fromTab, toTab); + } + } + } + } + movePhase = 0; + moveSelection.clear(); + initGui(); + } + } + + // ==================== DATA FROM SERVER ==================== + @Override + public void save() { + } + + @Override + public void setGuiData(NBTTagCompound compound) { + if (compound.hasKey("CloneFolders")) { + NBTTagList folderList = compound.getTagList("CloneFolders", 10); + folderNames.clear(); + for (int i = 0; i < folderList.tagCount(); i++) { + CloneFolder folder = new CloneFolder(); + folder.readNBT(folderList.getCompoundTagAt(i)); + folderNames.add(folder.name); + } + initGui(); + } else if (compound.hasKey("MoveSuccess")) { + initGui(); + } else if (compound.hasKey("CloneTags")) { + if (GuiNpcMobSpawner.activeFolder != null) { + this.tagMap = new TagMap(GuiNpcMobSpawner.activeFolder); + } else { + this.tagMap = new TagMap(GuiNpcMobSpawner.activeTab); + } + NBTTagCompound cloneTags = compound.getCompoundTag("CloneTags"); + this.tagMap.readNBT(cloneTags); + } else if (compound.hasKey("AllTags")) { + NBTTagList validTags = compound.getTagList("AllTags", 10); + if (validTags != null) { + HashMap tagsUpdate = new HashMap<>(); + HashMap tagNamesUpdate = new HashMap<>(); + for (int j = 0; j < validTags.tagCount(); j++) { + NBTTagCompound tagStructure = validTags.getCompoundTagAt(j); + Tag tag = new Tag(); + tag.readNBT(tagStructure); + tagsUpdate.put(tag.uuid, tag); + tagNamesUpdate.put(tag.name, tag.uuid); + } + GuiNpcMobSpawner.tags = tagsUpdate; + GuiNpcMobSpawner.tagNames = tagNamesUpdate; + this.tagList = new ArrayList(GuiNpcMobSpawner.tagNames.keySet()); + filterScroll.setList(getTagList()); + } + } else { + List list = new ArrayList(); + if (GuiNpcMobSpawner.sortType == 1) { + NBTTagList nbtlist = compound.getTagList("ListDate", 8); + for (int i = 0; i < nbtlist.tagCount(); i++) { + list.add(nbtlist.getStringTagAt(i)); + } + } else { + NBTTagList nbtlist = compound.getTagList("List", 8); + for (int i = 0; i < nbtlist.tagCount(); i++) { + list.add(nbtlist.getStringTagAt(i)); + } + } + + this.list = list; + populateRawListWithTags(); + scroll.setList(getSearchList(), GuiNpcMobSpawner.ascending == 0, GuiNpcMobSpawner.sortType == 0); + } + } +} diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiCloneFolderName.java b/src/main/java/noppes/npcs/client/gui/SubGuiCloneFolderName.java new file mode 100644 index 000000000..508b00518 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/SubGuiCloneFolderName.java @@ -0,0 +1,67 @@ +package noppes.npcs.client.gui; + +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.GuiNpcTextField; +import noppes.npcs.client.gui.util.SubGuiInterface; +import noppes.npcs.controllers.data.CloneFolder; + +public class SubGuiCloneFolderName extends SubGuiInterface { + private String originalName; + public boolean cancelled = true; + private String folderName = ""; + + public SubGuiCloneFolderName(String existingName) { + this.originalName = existingName; + this.folderName = existingName; + xSize = 200; + ySize = 80; + this.closeOnEsc = true; + } + + @Override + public void initGui() { + super.initGui(); + + String title = isRename() ? "Rename Folder" : "New Folder"; + addLabel(new GuiNpcLabel(0, title, guiLeft + 5, guiTop + 5)); + addTextField(new GuiNpcTextField(0, this, fontRendererObj, guiLeft + 5, guiTop + 20, 190, 20, folderName)); + + addButton(new GuiNpcButton(0, guiLeft + 5, guiTop + 50, 90, 20, "gui.done")); + addButton(new GuiNpcButton(1, guiLeft + 100, guiTop + 50, 90, 20, "gui.cancel")); + } + + @Override + protected void actionPerformed(GuiButton guibutton) { + if (guibutton.id == 0) { + // Done + folderName = getTextField(0).getText().trim(); + if (CloneFolder.isValidName(folderName)) { + cancelled = false; + close(); + } + } + if (guibutton.id == 1) { + // Cancel + cancelled = true; + close(); + } + } + + public boolean isRename() { + return originalName != null && !originalName.isEmpty(); + } + + public String getFolderName() { + return folderName; + } + + public String getOriginalName() { + return originalName; + } + + @Override + public void save() { + } +} diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiFolderSelect.java b/src/main/java/noppes/npcs/client/gui/SubGuiFolderSelect.java new file mode 100644 index 000000000..3ec727543 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/SubGuiFolderSelect.java @@ -0,0 +1,74 @@ +package noppes.npcs.client.gui; + +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiCustomScroll; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.ICustomScrollListener; +import noppes.npcs.client.gui.util.SubGuiInterface; + +import java.util.ArrayList; +import java.util.List; + +public class SubGuiFolderSelect extends SubGuiInterface implements ICustomScrollListener { + + public String selectedFolder; + private final List folderList; + private GuiCustomScroll folderScroll; + + public SubGuiFolderSelect(List folders, String currentSelection) { + this.folderList = new ArrayList<>(folders); + this.selectedFolder = currentSelection; + setBackground("menubg.png"); + xSize = 200; + ySize = 220; + closeOnEsc = true; + } + + @Override + public void initGui() { + super.initGui(); + + folderScroll = new GuiCustomScroll(this, 0); + folderScroll.setSize(xSize - 8, ySize - 50); + folderScroll.guiLeft = guiLeft + 4; + folderScroll.guiTop = guiTop + 4; + folderScroll.setList(new ArrayList<>(folderList)); + if (selectedFolder != null) { + folderScroll.setSelected(selectedFolder); + } + addScroll(folderScroll); + + int btnW = (xSize - 12) / 2; + addButton(new GuiNpcButton(0, guiLeft + 4, guiTop + ySize - 44, btnW, 20, "gui.done")); + addButton(new GuiNpcButton(1, guiLeft + 4 + btnW + 4, guiTop + ySize - 44, btnW, 20, "gui.cancel")); + + getButton(0).enabled = folderScroll.hasSelected(); + } + + @Override + public void customScrollClicked(int i, int j, int k, GuiCustomScroll guiCustomScroll) { + getButton(0).enabled = folderScroll.hasSelected(); + } + + @Override + public void customScrollDoubleClicked(String selection, GuiCustomScroll guiCustomScroll) { + selectedFolder = selection; + close(); + } + + @Override + protected void actionPerformed(GuiButton guibutton) { + if (guibutton.id == 0) { + selectedFolder = folderScroll.getSelected(); + close(); + } + if (guibutton.id == 1) { + selectedFolder = null; + close(); + } + } + + @Override + public void save() { + } +} diff --git a/src/main/java/noppes/npcs/client/gui/SubGuiMoveConfirm.java b/src/main/java/noppes/npcs/client/gui/SubGuiMoveConfirm.java new file mode 100644 index 000000000..ee70367f7 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/SubGuiMoveConfirm.java @@ -0,0 +1,57 @@ +package noppes.npcs.client.gui; + +import net.minecraft.client.gui.GuiButton; +import noppes.npcs.client.gui.util.GuiNpcButton; +import noppes.npcs.client.gui.util.GuiNpcLabel; +import noppes.npcs.client.gui.util.SubGuiInterface; + +public class SubGuiMoveConfirm extends SubGuiInterface { + public boolean confirmed = false; + + private final int count; + public final int destTab; + public final String destFolder; + + public SubGuiMoveConfirm(int count, int destTab, String destFolder) { + this.count = count; + this.destTab = destTab; + this.destFolder = destFolder; + xSize = 220; + ySize = 90; + closeOnEsc = true; + } + + @Override + public void initGui() { + super.initGui(); + + String line1 = "Move " + count + " clone" + (count != 1 ? "s" : "") + " to:"; + addLabel(new GuiNpcLabel(0, line1, guiLeft + 10, guiTop + 10, 0xFFFFFF)); + + String dest; + if (destFolder != null) { + dest = "Folder: \"" + destFolder + "\""; + } else { + dest = "Tab: " + destTab; + } + addLabel(new GuiNpcLabel(1, " " + dest, guiLeft + 10, guiTop + 25, 0xFFFFFF)); + + addButton(new GuiNpcButton(0, guiLeft + 10, guiTop + 55, 95, 20, "gui.yes")); + addButton(new GuiNpcButton(1, guiLeft + 115, guiTop + 55, 95, 20, "gui.cancel")); + } + + @Override + protected void actionPerformed(GuiButton guibutton) { + if (guibutton.id == 0) { + confirmed = true; + close(); + } + if (guibutton.id == 1) { + close(); + } + } + + @Override + public void save() { + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java index 3f3ca9223..e726239b1 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScroll.java @@ -367,6 +367,13 @@ public class GuiCustomScroll extends GuiScreen { return this; } + public void setSelectable(boolean selectable) { + this.selectable = selectable; + if (!selectable) { + hover = -1; + } + } + public boolean isMouseOver(int x, int y) { return x >= this.guiLeft && x <= this.guiLeft + this.xSize && y >= this.guiTop && y <= this.guiTop + this.ySize; } diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java index a69d449e7..b147a3af9 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollCloner.java @@ -1,5 +1,6 @@ package noppes.npcs.client.gui.util; +import net.minecraft.client.gui.GuiScreen; import net.minecraft.util.StatCollector; import noppes.npcs.client.gui.GuiNpcMobSpawner; import noppes.npcs.controllers.data.Tag; @@ -7,10 +8,10 @@ import noppes.npcs.controllers.data.Tag; import java.util.UUID; public class GuiCustomScrollCloner extends GuiCustomScroll { - private final GuiNpcMobSpawner parent; + private final IClonerGui parent; - public GuiCustomScrollCloner(GuiNpcMobSpawner parent, int id) { - super(parent, id); + public GuiCustomScrollCloner(IClonerGui parent, int id) { + super((GuiScreen) parent, id); this.parent = parent; } @@ -38,24 +39,25 @@ public class GuiCustomScrollCloner extends GuiCustomScroll { text += "..."; } else text = displayString; - if ((multipleSelection && selectedList.contains(text)) || (!multipleSelection && selected == i)) { + int itemColor = this.colors.getOrDefault(list.get(i), 0xffffff); + if ((multipleSelection && selectedList.contains(list.get(i))) || (!multipleSelection && selected == i)) { drawVerticalLine(j - 2, k - 4, k + 10, 0xffffffff); drawVerticalLine(j + xSize - 18 + xOffset, k - 4, k + 10, 0xffffffff); drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k - 3, 0xffffffff); drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k + 10, 0xffffffff); - fontRendererObj.drawString(text, j, k, this.colors.getOrDefault(text, 0xffffff)); + fontRendererObj.drawString(text, j, k, itemColor); } else if (i == hover) { fontRendererObj.drawString(text, j, k, 0x00ff00); } else { - fontRendererObj.drawString(text, j, k, this.colors.getOrDefault(text, 0xffffff)); + fontRendererObj.drawString(text, j, k, itemColor); } - if ((this.parent.getShowingClones() == 0 || this.parent.getShowingClones() == 2) && this.parent.tagMap != null) { + if ((parent.getShowingClones() == 0 || parent.getShowingClones() == 2) && parent.getTagMap() != null) { int tagStartX = j + fontRendererObj.getStringWidth(displayString) + 3; if (GuiNpcMobSpawner.displayTags == 0 || GuiNpcMobSpawner.displayTags == 1) { - if (this.parent.tagMap.hasClone(list.get(i))) { - for (UUID tagUUID : this.parent.tagMap.getUUIDsList(list.get(i))) { - Tag tag = this.parent.tags.get(tagUUID); + if (parent.getTagMap().hasClone(list.get(i))) { + for (UUID tagUUID : parent.getTagMap().getUUIDsList(list.get(i))) { + Tag tag = parent.getTags().get(tagUUID); if (tag != null) { if (GuiNpcMobSpawner.displayTags == 1 || !tag.getIsHidden()) { fontRendererObj.drawString("[" + tag.name + "]", tagStartX, k, tag.color); diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollIcons.java b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollIcons.java new file mode 100644 index 000000000..78686dac0 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/GuiCustomScrollIcons.java @@ -0,0 +1,108 @@ +package noppes.npcs.client.gui.util; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiScreen; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.StatCollector; +import org.lwjgl.opengl.GL11; + +import java.util.ArrayList; +import java.util.List; + +public class GuiCustomScrollIcons extends GuiCustomScroll { + public static final int ICON_NONE = 0; + public static final int ICON_TAB = 1; + public static final int ICON_FOLDER = 2; + + private static final ResourceLocation TAB_TEXTURE = new ResourceLocation("customnpcs", "textures/gui/cloner/tab.png"); + private static final ResourceLocation FOLDER_TEXTURE = new ResourceLocation("customnpcs", "textures/gui/cloner/folder.png"); + + private static final int ICON_RENDER_SIZE = 10; + private static final int ICON_PADDING = 14; + + private List iconTypes = new ArrayList<>(); + + public GuiCustomScrollIcons(GuiScreen parent, int id) { + super(parent, id); + } + + public void setListWithIcons(List names, List icons) { + this.setUnsortedList(names); + this.iconTypes = new ArrayList<>(icons); + } + + @Override + protected void drawItems() { + Minecraft mc = Minecraft.getMinecraft(); + for (int i = 0; i < list.size(); i++) { + int j = 4; + int k = (14 * i + 4) - scrollY; + if (k >= 4 && k + 12 < ySize) { + int xOffset = scrollHeight < ySize - 8 ? 0 : 10; + + int iconType = (i < iconTypes.size()) ? iconTypes.get(i) : ICON_NONE; + int textX = j; + if (iconType != ICON_NONE) { + textX = j + ICON_PADDING; + } + + // Draw icon texture + if (iconType == ICON_FOLDER || iconType == ICON_TAB) { + ResourceLocation tex = (iconType == ICON_FOLDER) ? FOLDER_TEXTURE : TAB_TEXTURE; + GL11.glColor4f(1.0F, 1.0F, 1.0F, 1.0F); + mc.getTextureManager().bindTexture(tex); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + drawFullTexture(j, k - 1, ICON_RENDER_SIZE, ICON_RENDER_SIZE); + GL11.glDisable(GL11.GL_BLEND); + } + + // Draw text + String displayString = StatCollector.translateToLocal(list.get(i)); + String text = ""; + float maxWidth = (xSize + xOffset - 8 - (iconType != ICON_NONE ? ICON_PADDING : 0)) * 0.8f; + if (fontRendererObj.getStringWidth(displayString) > maxWidth) { + for (int h = 0; h < displayString.length(); h++) { + char c = displayString.charAt(h); + text += c; + if (fontRendererObj.getStringWidth(text) > maxWidth) + break; + } + if (displayString.length() > text.length()) + text += "..."; + } else { + text = displayString; + } + + int itemColor = this.colors.getOrDefault(list.get(i), 0xffffff); + + if ((multipleSelection && selectedList.contains(list.get(i))) || (!multipleSelection && selected == i)) { + drawVerticalLine(j - 2, k - 4, k + 10, 0xffffffff); + drawVerticalLine(j + xSize - 18 + xOffset, k - 4, k + 10, 0xffffffff); + drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k - 3, 0xffffffff); + drawHorizontalLine(j - 2, j + xSize - 18 + xOffset, k + 10, 0xffffffff); + fontRendererObj.drawString(text, textX, k, itemColor); + } else if (i == hover) { + fontRendererObj.drawString(text, textX, k, 0x00ff00); + } else { + fontRendererObj.drawString(text, textX, k, itemColor); + } + } + } + } + + /** + * Draw a full texture (UV 0,0 to 1,1) scaled to the given width/height. + * Works with any texture size (not just 256x256). + */ + private void drawFullTexture(int x, int y, int w, int h) { + Tessellator tessellator = Tessellator.instance; + tessellator.startDrawingQuads(); + tessellator.addVertexWithUV(x, y + h, 0, 0, 1); + tessellator.addVertexWithUV(x + w, y + h, 0, 1, 1); + tessellator.addVertexWithUV(x + w, y, 0, 1, 0); + tessellator.addVertexWithUV(x, y, 0, 0, 0); + tessellator.draw(); + } +} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java b/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java new file mode 100644 index 000000000..750c60668 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/GuiDirectory.java @@ -0,0 +1,206 @@ +package noppes.npcs.client.gui.util; + +/** + * Abstract base class for fullscreen directory-browsing GUIs. + * Provides a configurable three-panel layout (left nav, center content, right actions) + * with percentage-based sizing, panel backgrounds/borders, and an optional resizable divider. + * + * Subclasses override abstract hooks to populate each panel region. + * Designed for reuse across Cloner, Quest Manager, Dialog Manager, etc. + */ +public abstract class GuiDirectory extends GuiNPCInterface implements ICustomScrollListener, ISubGuiListener { + + // ===== LAYOUT CONFIG (subclasses may override in constructor) ===== + protected float leftPanelPercent = 0.20f; + protected float rightPanelPercent = 0.10f; + protected int minLeftPanelW = 80; + protected int minRightPanelW = 40; + protected int minCenterW = 100; + + // ===== LAYOUT CONSTANTS ===== + protected int pad = 10; + protected int topBarH = 24; + protected int gap = 4; + protected int btnH = 20; + + // ===== PANEL COLORS ===== + protected int panelBg = 0xC0101010; + protected int panelBorder = 0xFF333333; + protected int topBarBg = 0xC0181818; + + // ===== COMPUTED LAYOUT (populated by computeLayout) ===== + protected int originX, originY, usableW, usableH; + protected int leftPanelW, rightPanelW; + protected int contentX, contentY, contentW, contentH; + protected int rightX; + + // ===== OPTIONAL RESIZABLE DIVIDER (between left and center) ===== + protected boolean enableDivider = false; + protected int dividerWidth = 5; + protected int dividerLineHeight = 20; + protected int minDividerPanelW = 50; + private int dividerOffset; + private boolean isDragging = false; + private int dragStartX; + + // ===== CONSTRUCTOR ===== + public GuiDirectory() { + super(); + closeOnEsc = true; + drawDefaultBackground = true; + } + + // ===== LAYOUT COMPUTATION ===== + protected void computeLayout() { + originX = pad; + originY = pad; + usableW = width - 2 * pad; + usableH = height - 2 * pad; + + leftPanelW = Math.max(minLeftPanelW, (int)(usableW * leftPanelPercent)); + rightPanelW = Math.max(minRightPanelW, (int)(usableW * rightPanelPercent)); + + if (rightPanelPercent <= 0) { + rightPanelW = 0; + } + + int gapCount = rightPanelW > 0 ? 3 : 2; + contentW = usableW - leftPanelW - rightPanelW - gapCount * gap; + if (contentW < minCenterW) { + contentW = minCenterW; + } + + contentX = originX + leftPanelW + gap; + contentY = originY + topBarH + gap; + contentH = usableH - topBarH - gap; + rightX = contentX + contentW + gap; + + if (enableDivider && dividerOffset == 0) { + dividerOffset = leftPanelW; + } + } + + // ===== INIT GUI ===== + @Override + public void initGui() { + super.initGui(); + computeLayout(); + initTopBar(originY + 2); + initLeftPanel(); + initCenterPanel(); + if (rightPanelW > 0) { + initRightPanel(contentY + 4); + } + } + + // ===== DRAWING ===== + @Override + public void drawScreen(int mouseX, int mouseY, float partialTicks) { + drawTopBar(); + drawPanels(); + if (enableDivider) { + drawDivider(mouseX, mouseY); + } + super.drawScreen(mouseX, mouseY, partialTicks); + drawOverlay(mouseX, mouseY, partialTicks); + } + + protected void drawTopBar() { + GuiUtil.drawRectD(originX, originY, originX + usableW, originY + topBarH, topBarBg); + } + + protected void drawPanels() { + // Left nav panel border + GuiUtil.drawRectD(originX - 1, contentY - 1, originX + leftPanelW + 1, originY + usableH + 1, panelBorder); + + // Right action panel border (if present) + if (rightPanelW > 0) { + GuiUtil.drawRectD(rightX - 1, contentY - 1, rightX + rightPanelW + 1, originY + usableH + 1, panelBorder); + } + } + + protected void drawDivider(int mouseX, int mouseY) { + if (!enableDivider) return; + int divX = originX + dividerOffset; + int regionTop = contentY; + int regionHeight = contentH; + int handleTop = regionTop + (regionHeight - dividerLineHeight) / 2; + drawRect(divX + 1, handleTop, divX + dividerWidth - 1, handleTop + dividerLineHeight, 0xFF707070); + } + + /** Override for extra drawing after super.drawScreen (e.g. status text) */ + protected void drawOverlay(int mouseX, int mouseY, float partialTicks) { + } + + // ===== DIVIDER MOUSE HANDLING ===== + @Override + public void mouseClicked(int mouseX, int mouseY, int mouseButton) { + if (enableDivider && !hasSubGui()) { + int divX = originX + dividerOffset; + int regionTop = contentY; + int handleTop = regionTop + (contentH - dividerLineHeight) / 2; + int handleBottom = handleTop + dividerLineHeight; + if (mouseX >= divX && mouseX <= divX + dividerWidth && + mouseY >= handleTop && mouseY <= handleBottom) { + isDragging = true; + resizingActive = true; + dragStartX = mouseX; + return; + } + } + super.mouseClicked(mouseX, mouseY, mouseButton); + } + + @Override + public void mouseClickMove(int mouseX, int mouseY, int clickedMouseButton, long timeSinceLastClick) { + if (isDragging) { + int dx = mouseX - dragStartX; + dragStartX = mouseX; + dividerOffset += dx; + + int maxOffset = (usableW - (rightPanelW > 0 ? rightPanelW + gap : 0)) - dividerWidth - minDividerPanelW; + dividerOffset = Math.max(minDividerPanelW, Math.min(dividerOffset, maxOffset)); + + onDividerMoved(dividerOffset); + return; + } + super.mouseClickMove(mouseX, mouseY, clickedMouseButton, timeSinceLastClick); + } + + @Override + protected void mouseMovedOrUp(int mouseX, int mouseY, int state) { + if (isDragging) { + isDragging = false; + resizingActive = false; + return; + } + super.mouseMovedOrUp(mouseX, mouseY, state); + } + + /** Called when divider is dragged. Override to update scroll sizes. */ + protected void onDividerMoved(int newOffset) { + } + + // Accessors for divider offset + protected int getDividerOffset() { + return dividerOffset; + } + + protected void setDividerOffset(int offset) { + this.dividerOffset = offset; + } + + // ===== ABSTRACT HOOKS (subclass must implement) ===== + + /** Populate top bar buttons. topBtnY is the y-coordinate for the button row. */ + protected abstract void initTopBar(int topBtnY); + + /** Populate the left navigation panel (scrolls, search fields, buttons). */ + protected abstract void initLeftPanel(); + + /** Populate the center content panel (main scroll list). */ + protected abstract void initCenterPanel(); + + /** Populate the right action panel. startY is the first button y-coordinate. */ + protected abstract void initRightPanel(int startY); +} diff --git a/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java b/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java index 0aa6c7d3d..94398bb60 100644 --- a/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java +++ b/src/main/java/noppes/npcs/client/gui/util/GuiMenuSideButton.java @@ -15,6 +15,7 @@ public class GuiMenuSideButton extends GuiNpcButton { public boolean active; public boolean rightSided; + public int textColor = -1; public ItemStack renderStack; public ResourceLocation renderResource; public int renderIconPosX = 0; @@ -96,13 +97,15 @@ public class GuiMenuSideButton extends GuiNpcButton { text += "..."; } else text = displayString; - if (active) { - drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xffffa0); - } else if (field_146123_n) { - drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xffffa0); + int color; + if (textColor != -1) { + color = textColor; + } else if (active || field_146123_n) { + color = 0xffffa0; } else { - drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, 0xe0e0e0); + color = 0xe0e0e0; } + drawCenteredString(fontrenderer, text, xPosition + width / 2, yPosition + (height - 8) / 2, color); } @Override diff --git a/src/main/java/noppes/npcs/client/gui/util/IClonerGui.java b/src/main/java/noppes/npcs/client/gui/util/IClonerGui.java new file mode 100644 index 000000000..db4989b56 --- /dev/null +++ b/src/main/java/noppes/npcs/client/gui/util/IClonerGui.java @@ -0,0 +1,13 @@ +package noppes.npcs.client.gui.util; + +import noppes.npcs.controllers.data.Tag; +import noppes.npcs.controllers.data.TagMap; + +import java.util.HashMap; +import java.util.UUID; + +public interface IClonerGui { + int getShowingClones(); + HashMap getTags(); + TagMap getTagMap(); +} diff --git a/src/main/java/noppes/npcs/controllers/ServerCloneController.java b/src/main/java/noppes/npcs/controllers/ServerCloneController.java index ea61a0112..32bd0e15c 100644 --- a/src/main/java/noppes/npcs/controllers/ServerCloneController.java +++ b/src/main/java/noppes/npcs/controllers/ServerCloneController.java @@ -15,6 +15,7 @@ import noppes.npcs.api.IPos; import noppes.npcs.api.IWorld; import noppes.npcs.api.entity.IEntity; import noppes.npcs.api.handler.ICloneHandler; +import noppes.npcs.controllers.data.CloneFolder; import noppes.npcs.controllers.data.TagMap; import noppes.npcs.scripted.CustomNPCsException; import noppes.npcs.scripted.NpcAPI; @@ -27,6 +28,7 @@ import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -34,8 +36,11 @@ import java.util.UUID; public class ServerCloneController implements ICloneHandler { public static ServerCloneController Instance; + protected Map folders = new LinkedHashMap<>(); + public ServerCloneController() { loadClones(); + loadFolders(); } private void loadClones() { @@ -103,6 +108,8 @@ public class ServerCloneController implements ICloneHandler { return clones; } + // ==================== Tab-Based Clone Operations ==================== + public NBTTagCompound getCloneData(ICommandSender player, String name, int tab) { File file = new File(new File(getDir(), tab + ""), name + ".json"); if (!file.exists()) { @@ -194,6 +201,250 @@ public class ServerCloneController implements ICloneHandler { return name; } + // ==================== Folder Registry ==================== + + public void loadFolders() { + folders.clear(); + File dir = getDir(); + File[] files = dir.listFiles(); + if (files != null) { + List folderDirs = new ArrayList<>(); + for (File f : files) { + if (!f.isDirectory()) continue; + String name = f.getName(); + if (name.startsWith("___")) continue; + try { + Integer.parseInt(name); + continue; + } catch (NumberFormatException ignored) { + } + folderDirs.add(f); + } + folderDirs.sort(Comparator.comparing(File::getName)); + for (File f : folderDirs) { + CloneFolder folder = new CloneFolder(f.getName()); + folder.createdDate = f.lastModified(); + folders.put(f.getName(), folder); + } + } + } + + public List getFolderList() { + return new ArrayList<>(folders.values()); + } + + public List getFolderNames() { + return new ArrayList<>(folders.keySet()); + } + + public boolean hasFolder(String name) { + return folders.containsKey(name); + } + + // ==================== Folder CRUD ==================== + + public CloneFolder createFolder(String name) { + if (!CloneFolder.isValidName(name) || folders.containsKey(name)) { + return null; + } + CloneFolder folder = new CloneFolder(name); + File dir = getFolderDir(name); + if (!dir.exists()) { + dir.mkdir(); + } + folders.put(name, folder); + return folder; + } + + public boolean renameFolder(String oldName, String newName) { + if (!folders.containsKey(oldName) || !CloneFolder.isValidName(newName) || folders.containsKey(newName)) { + return false; + } + CloneFolder folder = folders.get(oldName); + File oldDir = getFolderDir(oldName); + File newDir = new File(getDir(), newName); + if (!oldDir.renameTo(newDir)) { + return false; + } + folders.remove(oldName); + folder.name = newName; + folders.put(newName, folder); + return true; + } + + public boolean deleteFolder(String name) { + if (!folders.containsKey(name)) { + return false; + } + List clones = getClones(name); + if (!clones.isEmpty()) { + return false; + } + File dir = getFolderDir(name); + if (dir.exists()) { + File tagMapFile = new File(dir, "___tagmap.dat"); + if (tagMapFile.exists()) tagMapFile.delete(); + File tagMapOld = new File(dir, "___tagmap.dat_old"); + if (tagMapOld.exists()) tagMapOld.delete(); + File tagMapNew = new File(dir, "___tagmap.dat_new"); + if (tagMapNew.exists()) tagMapNew.delete(); + dir.delete(); + } + folders.remove(name); + return true; + } + + // ==================== Folder-Based Clone Operations ==================== + + public File getFolderDir(String folderName) { + return new File(getDir(), folderName); + } + + public List getClones(String folderName) { + List list = new ArrayList<>(); + File dir = getFolderDir(folderName); + if (!dir.exists() || !dir.isDirectory()) + return list; + for (String file : dir.list()) { + if (file.endsWith(".json")) + list.add(file.substring(0, file.length() - 5)); + } + return list; + } + + public List getClonesDate(String folderName) { + List list = new ArrayList<>(); + File dir = getFolderDir(folderName); + if (!dir.exists() || !dir.isDirectory()) + return list; + File[] files = dir.listFiles(); + if (files == null) return list; + Arrays.sort(files, new Comparator() { + public int compare(File f1, File f2) { + return Long.compare(f1.lastModified(), f2.lastModified()); + } + }); + for (File file : files) { + String fileName = file.getName(); + if (fileName.endsWith(".json")) + list.add(fileName.substring(0, fileName.length() - 5)); + } + return list; + } + + public NBTTagCompound getCloneData(ICommandSender player, String name, String folderName) { + File file = new File(getFolderDir(folderName), name + ".json"); + if (!file.exists()) { + if (player != null) + player.addChatMessage(new ChatComponentText("Could not find clone file")); + return null; + } + try { + return NBTJsonUtil.LoadFile(file); + } catch (Exception e) { + LogWriter.error("Error loading: " + file.getAbsolutePath(), e); + if (player != null) + player.addChatMessage(new ChatComponentText(e.getMessage())); + } + return null; + } + + public void saveClone(String folderName, String name, NBTTagCompound compound) { + try { + File dir = getFolderDir(folderName); + if (!dir.exists()) dir.mkdir(); + String filename = name + ".json"; + File file = new File(dir, filename + "_new"); + File file2 = new File(dir, filename); + NBTJsonUtil.SaveFile(file, compound); + addToTagMap(compound, name, folderName); + if (file2.exists()) { + file2.delete(); + } + file.renameTo(file2); + } catch (Exception e) { + LogWriter.except(e); + } + } + + public boolean removeClone(String name, String folderName) { + File file = new File(getFolderDir(folderName), name + ".json"); + if (!file.exists()) + return false; + file.delete(); + removeFromTagMap(name, folderName); + return true; + } + + public String addClone(NBTTagCompound nbttagcompound, String name, String folderName) { + cleanTags(nbttagcompound); + saveClone(folderName, name, nbttagcompound); + return name; + } + + public String addClone(NBTTagCompound nbttagcompound, String name, String folderName, NBTTagCompound tempTags) { + cleanTagList(nbttagcompound, tempTags); + cleanTags(nbttagcompound); + saveClone(folderName, name, nbttagcompound); + return name; + } + + // ==================== Move Operations ==================== + + public boolean moveClone(String cloneName, int fromTab, String toFolder) { + NBTTagCompound data = getCloneData(null, cloneName, fromTab); + if (data == null) return false; + + HashSet tagUUIDs = getTagUUIDsFromTab(cloneName, fromTab); + + saveClone(toFolder, cloneName, data); + setTagUUIDsForFolder(cloneName, toFolder, tagUUIDs); + + removeClone(cloneName, fromTab); + return true; + } + + public boolean moveClone(String cloneName, String fromFolder, int toTab) { + NBTTagCompound data = getCloneData(null, cloneName, fromFolder); + if (data == null) return false; + + HashSet tagUUIDs = getTagUUIDsFromFolder(cloneName, fromFolder); + + saveClone(toTab, cloneName, data); + setTagUUIDsForTab(cloneName, toTab, tagUUIDs); + + removeClone(cloneName, fromFolder); + return true; + } + + public boolean moveClone(String cloneName, String fromFolder, String toFolder) { + NBTTagCompound data = getCloneData(null, cloneName, fromFolder); + if (data == null) return false; + + HashSet tagUUIDs = getTagUUIDsFromFolder(cloneName, fromFolder); + + saveClone(toFolder, cloneName, data); + setTagUUIDsForFolder(cloneName, toFolder, tagUUIDs); + + removeClone(cloneName, fromFolder); + return true; + } + + public boolean moveClone(String cloneName, int fromTab, int toTab) { + NBTTagCompound data = getCloneData(null, cloneName, fromTab); + if (data == null) return false; + + HashSet tagUUIDs = getTagUUIDsFromTab(cloneName, fromTab); + + saveClone(toTab, cloneName, data); + setTagUUIDsForTab(cloneName, toTab, tagUUIDs); + + removeClone(cloneName, fromTab); + return true; + } + + // ==================== Tag Utilities ==================== + public NBTTagCompound cleanTagList(NBTTagCompound nbttagcompound, NBTTagCompound tempTags) { HashSet tagUUIDs = new HashSet(); if (nbttagcompound.hasKey("TagUUIDs")) { @@ -243,6 +494,25 @@ public class ServerCloneController implements ICloneHandler { return true; } + public boolean addToTagMap(NBTTagCompound nbttagcompound, String name, String folderName) { + HashSet tagUUIDs = new HashSet<>(); + if (nbttagcompound.hasKey("TagUUIDs")) { + NBTTagList nbtTagList = nbttagcompound.getTagList("TagUUIDs", 8); + for (int i = 0; i < nbtTagList.tagCount(); i++) { + tagUUIDs.add(UUID.fromString(nbtTagList.getStringTagAt(i))); + } + } + + TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName); + if (!tagUUIDs.isEmpty()) { + tagMap.putClone(name, tagUUIDs); + } else { + tagMap.removeClone(name); + } + ServerTagMapController.Instance.saveTagMap(tagMap); + return true; + } + public boolean removeFromTagMap(String name, int tab) { TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab); if (tagMap.removeClone(name)) { @@ -252,6 +522,41 @@ public class ServerCloneController implements ICloneHandler { return false; } + public boolean removeFromTagMap(String name, String folderName) { + TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName); + if (tagMap.removeClone(name)) { + ServerTagMapController.Instance.saveTagMap(tagMap); + return true; + } + return false; + } + + protected HashSet getTagUUIDsFromTab(String cloneName, int tab) { + TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab); + HashSet uuids = tagMap.getUUIDs(cloneName); + return uuids != null ? new HashSet<>(uuids) : new HashSet<>(); + } + + protected HashSet getTagUUIDsFromFolder(String cloneName, String folderName) { + TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName); + HashSet uuids = tagMap.getUUIDs(cloneName); + return uuids != null ? new HashSet<>(uuids) : new HashSet<>(); + } + + protected void setTagUUIDsForTab(String cloneName, int tab, HashSet tagUUIDs) { + if (tagUUIDs == null || tagUUIDs.isEmpty()) return; + TagMap tagMap = ServerTagMapController.Instance.getTagMap(tab); + tagMap.putClone(cloneName, tagUUIDs); + ServerTagMapController.Instance.saveTagMap(tagMap); + } + + protected void setTagUUIDsForFolder(String cloneName, String folderName, HashSet tagUUIDs) { + if (tagUUIDs == null || tagUUIDs.isEmpty()) return; + TagMap tagMap = ServerTagMapController.Instance.getTagMap(folderName); + tagMap.putClone(cloneName, tagUUIDs); + ServerTagMapController.Instance.saveTagMap(tagMap); + } + public void cleanTags(NBTTagCompound nbttagcompound) { if (nbttagcompound.hasKey("ItemGiverId")) nbttagcompound.setInteger("ItemGiverId", 0); @@ -288,6 +593,8 @@ public class ServerCloneController implements ICloneHandler { } } + // ==================== API Methods (ICloneHandler) ==================== + public IEntity spawn(double x, double y, double z, int tab, String name, IWorld world, boolean ignoreProtection) { NBTTagCompound compound = this.getCloneData((ICommandSender) null, name, tab); if (compound == null) { @@ -327,7 +634,7 @@ public class ServerCloneController implements ICloneHandler { for (File file : dir.listFiles()) { if (file.getName().endsWith(".json")) { NBTTagCompound compound = NBTJsonUtil.LoadFile(file); - Instance.cleanTags(compound); + cleanTags(compound); Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld()); arrayList.add(entity == null ? null : NpcAPI.Instance().getIEntity(entity)); } @@ -343,7 +650,7 @@ public class ServerCloneController implements ICloneHandler { if (compound == null) { throw new CustomNPCsException("Unknown clone tab:" + tab + " name:" + name, new Object[0]); } else { - Instance.cleanTags(compound); + cleanTags(compound); Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld()); return entity == null ? null : NpcAPI.Instance().getIEntity(entity); } @@ -354,7 +661,6 @@ public class ServerCloneController implements ICloneHandler { return compound != null; } - public void set(int tab, String name, IEntity entity) { NBTTagCompound compound = new NBTTagCompound(); if (!entity.getMCEntity().writeMountToNBT(compound)) @@ -367,4 +673,85 @@ public class ServerCloneController implements ICloneHandler { public void remove(int tab, String name) { this.removeClone(name, tab); } + + // --- Folder API Methods --- + + public String[] getFolders() { + return getFolderNames().toArray(new String[0]); + } + + public IEntity spawn(double x, double y, double z, String folderName, String name, IWorld world, boolean ignoreProtection) { + NBTTagCompound compound = getCloneData(null, name, folderName); + if (compound == null) { + throw new CustomNPCsException("Unknown clone folder:" + folderName + " name:" + name, new Object[0]); + } + Entity entity; + if (!ignoreProtection) { + entity = NoppesUtilServer.spawnCloneWithProtection(compound, (int) x, (int) y, (int) z, world.getMCWorld()); + } else { + entity = NoppesUtilServer.spawnClone(compound, (int) x, (int) y, (int) z, world.getMCWorld()); + } + return entity == null ? null : NpcAPI.Instance().getIEntity(entity); + } + + public IEntity spawn(IPos pos, String folderName, String name, IWorld world, boolean ignoreProtection) { + return this.spawn(pos.getX(), pos.getY(), pos.getZ(), folderName, name, world, ignoreProtection); + } + + public IEntity spawn(double x, double y, double z, String folderName, String name, IWorld world) { + return spawn(x, y, z, folderName, name, world, true); + } + + public IEntity spawn(IPos pos, String folderName, String name, IWorld world) { + return this.spawn(pos.getX(), pos.getY(), pos.getZ(), folderName, name, world); + } + + public IEntity[] getFolder(String folderName, IWorld world) { + File dir = getFolderDir(folderName); + if (!dir.exists() || !dir.isDirectory() || dir.listFiles() == null) { + return new IEntity[]{}; + } + + ArrayList arrayList = new ArrayList<>(); + try { + for (File file : dir.listFiles()) { + if (file.getName().endsWith(".json")) { + NBTTagCompound compound = NBTJsonUtil.LoadFile(file); + cleanTags(compound); + Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld()); + arrayList.add(entity == null ? null : NpcAPI.Instance().getIEntity(entity)); + } + } + } catch (Exception ignored) { + } + return arrayList.toArray(new IEntity[]{}); + } + + public IEntity get(String folderName, String name, IWorld world) { + NBTTagCompound compound = getCloneData(null, name, folderName); + if (compound == null) { + throw new CustomNPCsException("Unknown clone folder:" + folderName + " name:" + name, new Object[0]); + } + cleanTags(compound); + Entity entity = EntityList.createEntityFromNBT(compound, world.getMCWorld()); + return entity == null ? null : NpcAPI.Instance().getIEntity(entity); + } + + public boolean has(String folderName, String name) { + NBTTagCompound compound = getCloneData(null, name, folderName); + return compound != null; + } + + public void set(String folderName, String name, IEntity entity) { + NBTTagCompound compound = new NBTTagCompound(); + if (!entity.getMCEntity().writeMountToNBT(compound)) + throw new CustomNPCsException("Cannot save dead entities", new Object[0]); + + this.cleanTags(compound); + this.saveClone(folderName, name, compound); + } + + public void remove(String folderName, String name) { + this.removeClone(name, folderName); + } } diff --git a/src/main/java/noppes/npcs/controllers/ServerTagMapController.java b/src/main/java/noppes/npcs/controllers/ServerTagMapController.java index 0221481e3..42ca6afeb 100644 --- a/src/main/java/noppes/npcs/controllers/ServerTagMapController.java +++ b/src/main/java/noppes/npcs/controllers/ServerTagMapController.java @@ -35,6 +35,13 @@ public class ServerTagMapController { return dir; } + public File getCloneFolderDir(String folderName) { + File dir = new File(getDir(), folderName); + if (!dir.exists()) + dir.mkdir(); + return dir; + } + public TagMap getTagMap(int tab) { this.tagMap = new TagMap(tab); try { @@ -65,9 +72,33 @@ public class ServerTagMapController { this.tagMap.readNBT(nbtCompound); } + public TagMap getTagMap(String folderName) { + this.tagMap = new TagMap(folderName); + try { + File file = new File(getCloneFolderDir(folderName), "___tagmap.dat"); + if (file.exists()) { + loadTagMapFile(file); + } + } catch (Exception e) { + try { + File file = new File(getCloneFolderDir(folderName), "___tagmap.dat_old"); + if (file.exists()) { + loadTagMapFile(file); + } + } catch (Exception ignored) { + } + } + return this.tagMap; + } + public void saveTagMap(TagMap tagMap) { try { - File saveDir = getCloneTabDir(tagMap.cloneTab); + File saveDir; + if (tagMap.cloneFolder != null) { + saveDir = getCloneFolderDir(tagMap.cloneFolder); + } else { + saveDir = getCloneTabDir(tagMap.cloneTab); + } File file = new File(saveDir, "___tagmap.dat_new"); File file1 = new File(saveDir, "___tagmap.dat_old"); File file2 = new File(saveDir, "___tagmap.dat"); diff --git a/src/main/java/noppes/npcs/controllers/data/CloneFolder.java b/src/main/java/noppes/npcs/controllers/data/CloneFolder.java new file mode 100644 index 000000000..d2d0c7bcf --- /dev/null +++ b/src/main/java/noppes/npcs/controllers/data/CloneFolder.java @@ -0,0 +1,61 @@ +package noppes.npcs.controllers.data; + +import net.minecraft.nbt.NBTTagCompound; + +public class CloneFolder { + public String name; + public long createdDate; + + public CloneFolder() { + this.name = ""; + this.createdDate = System.currentTimeMillis(); + } + + public CloneFolder(String name) { + this.name = name; + this.createdDate = System.currentTimeMillis(); + } + + public void readNBT(NBTTagCompound compound) { + name = compound.getString("Name"); + createdDate = compound.getLong("Created"); + } + + public NBTTagCompound writeNBT(NBTTagCompound compound) { + compound.setString("Name", name); + compound.setLong("Created", createdDate); + return compound; + } + + public static boolean isValidName(String name) { + if (name == null || name.isEmpty()) { + return false; + } + if (name.length() > 32) { + return false; + } + if (!name.equals(name.trim())) { + return false; + } + if (name.equals(".") || name.equals("..")) { + return false; + } + if (name.startsWith("___")) { + return false; + } + try { + Integer.parseInt(name); + return false; + } catch (NumberFormatException ignored) { + } + for (char c : name.toCharArray()) { + if (c == '/' || c == '\\' || c == ':' || c == '*' || c == '?' || c == '"' || c == '<' || c == '>' || c == '|') { + return false; + } + if (c < 32) { + return false; + } + } + return true; + } +} diff --git a/src/main/java/noppes/npcs/controllers/data/TagMap.java b/src/main/java/noppes/npcs/controllers/data/TagMap.java index e4c0ad57f..2205d96fd 100644 --- a/src/main/java/noppes/npcs/controllers/data/TagMap.java +++ b/src/main/java/noppes/npcs/controllers/data/TagMap.java @@ -13,10 +13,18 @@ import java.util.UUID; public class TagMap { public int cloneTab; + public String cloneFolder; public HashMap> tagMap; public TagMap(int tab) { this.cloneTab = tab; + this.cloneFolder = null; + this.tagMap = new HashMap>(); + } + + public TagMap(String folder) { + this.cloneTab = -1; + this.cloneFolder = folder; this.tagMap = new HashMap>(); } diff --git a/src/main/resources/assets/customnpcs/textures/gui/cloner/folder.png b/src/main/resources/assets/customnpcs/textures/gui/cloner/folder.png new file mode 100644 index 000000000..d98408b92 Binary files /dev/null and b/src/main/resources/assets/customnpcs/textures/gui/cloner/folder.png differ diff --git a/src/main/resources/assets/customnpcs/textures/gui/cloner/tab.png b/src/main/resources/assets/customnpcs/textures/gui/cloner/tab.png new file mode 100644 index 000000000..a041d03b0 Binary files /dev/null and b/src/main/resources/assets/customnpcs/textures/gui/cloner/tab.png differ