diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..843b772 --- /dev/null +++ b/README.txt @@ -0,0 +1,41 @@ +GiftAPI +======= + +Плагин для Майнкрафт сервера с ядром Bukkit 1.6.4. +Это API + кастомный инвентарь. + +Использование: +-------------- +1. Скачайте релиз или соберите плагин сами. +2. Переместите в папку КОРНЕВАЯ-ПАПКА-СЕРВЕРА/plugins. +3. Запустите сервер или введите команду /reload. + +Документация: +------------- +1. 'add(player, item)' + - Описание: добавляет предмет в кастомные инвентарь игрока. + - Параметры: + - 'player' (Player): игрок которому нужно добавить предмет. + - 'item' (ItemStack): предмет который должен быть сохранен в инвентарь. + +2. 'remove(player, item)' + - Описание: удаляет предмет из кастомного инвентаря игрока. + - Параметры: + - 'player' (Player): игрок которому нужно удалить предмет. + - 'item' (ItemStack): предмет который должен быть удалён из инвентаря. + +3. 'openGUI()' + - Описание: открывает кастомный инвентарь игрока. + - Параметры: + - 'player' (Player): игрок которому нужно открыть кастомный инвентарь. + +Требования: +----------- +- Bukkit 1.6.4 + + +Лицензия: +--------- +Этот проект использует MIT лицензию. + +Смотреть больше: https://opensource.org/licenses/MIT \ No newline at end of file diff --git a/src/main/java/tokarotik/giftapi/APIManager.java b/src/main/java/tokarotik/giftapi/APIManager.java index cb3e8d9..029d4b4 100644 --- a/src/main/java/tokarotik/giftapi/APIManager.java +++ b/src/main/java/tokarotik/giftapi/APIManager.java @@ -7,38 +7,74 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; - import tokarotik.giftapi.inventory.InventoryManager; +/** + * Handles API-facing logic such as adding, removing, and opening + * gift inventories for players. Acts as a middle layer between plugin + * logic and internal inventory mechanics. + */ public class APIManager implements Listener { - - public final Main plugin; private final InventoryManager inventoryManager; - public APIManager(Main plugin, FileConfiguration config, int inventory_slots) - { - this.plugin = plugin; + public APIManager(Main plugin, FileConfiguration config, int inventorySlots) { + if (plugin == null || config == null) { + throw new IllegalArgumentException("Plugin and config must not be null."); + } + this.inventoryManager = new InventoryManager( plugin.getCacheManager(), - inventory_slots, - ConfigPaths.getWithColor(config, ConfigPaths.GUINAME, "GiftAPI Menu"), - ConfigPaths.getWithColor(config, ConfigPaths.GUIRIGHT, "<>"), - ConfigPaths.getWithColor(config, ConfigPaths.GUIEXIT, "quit") + inventorySlots, + ConfigPaths.getWithColor(config, ConfigPaths.GUI_TITLE, "GiftAPI Menu"), + ConfigPaths.getWithColor(config, ConfigPaths.GUI_BUTTON_RIGHT, "<>"), + ConfigPaths.getWithColor(config, ConfigPaths.GUI_BUTTON_EXIT, "quit") ); - Bukkit.getPluginManager().registerEvents(this.inventoryManager, this.plugin); + // Register inventory click listener + Bukkit.getPluginManager().registerEvents(this.inventoryManager, plugin); } - public synchronized void add(Player player, ItemStack item) { this.inventoryManager.add(player, item); } - - public synchronized void remove(Player player, ItemStack item) { this.inventoryManager.remove(player, item); } - - public synchronized void openInventory(Player player) - { - this.inventoryManager.openCustomInventory(player); + /** + * Adds an item to the player's gift inventory. + * + * @param player the target player + * @param item the item to add + */ + public void add(Player player, ItemStack item) { + if (player == null || item == null) return; + inventoryManager.add(player, item); } + /** + * Removes an item from the player's gift inventory. + * + * @param player the target player + * @param item the item to remove + */ + public void remove(Player player, ItemStack item) { + if (player == null || item == null) return; + inventoryManager.remove(player, item); + } + + /** + * Opens the custom inventory GUI for the specified player. + * + * @param player the player to open the inventory for + */ + public void openInventory(Player player) { + if (player == null) return; + inventoryManager.openCustomInventory(player); + } + + /** + * Delegates inventory click events to the inventory manager. + * + * @param event the inventory click event + */ @EventHandler - public void onInventoryClick(InventoryClickEvent event) { this.inventoryManager.onInventoryClick(event); } + public void handleInventoryClick(InventoryClickEvent event) { + inventoryManager.handleInventoryClick(event); + } } + diff --git a/src/main/java/tokarotik/giftapi/ConfigPaths.java b/src/main/java/tokarotik/giftapi/ConfigPaths.java index af85f5b..a3e684e 100644 --- a/src/main/java/tokarotik/giftapi/ConfigPaths.java +++ b/src/main/java/tokarotik/giftapi/ConfigPaths.java @@ -3,15 +3,35 @@ package tokarotik.giftapi; import org.bukkit.ChatColor; import org.bukkit.configuration.file.FileConfiguration; -public class ConfigPaths { - public static final String DEVTEST = "dev-test"; - public static final String GUINAME = "gui.title-menu"; - public static final String GUIRIGHT = "gui.buttons.right-name"; - public static final String GUILEFT = "gui.buttons.left-name"; - public static final String GUIEXIT = "gui.buttons.exit-name"; +/** + * Centralized configuration path keys and utility methods for retrieving + * and colorizing string values from the plugin's configuration. + */ +public final class ConfigPaths { - public static String getWithColor(FileConfiguration config, String path, String defaul) - { - return ChatColor.translateAlternateColorCodes('&', config.getString(path, defaul)); + private ConfigPaths() { + // Utility class; prevent instantiation + throw new UnsupportedOperationException("ConfigPaths is a utility class and cannot be instantiated."); + } + + // === Config Path Constants === + public static final String GUI_TITLE = "gui.title-menu"; + public static final String GUI_BUTTON_RIGHT = "gui.buttons.right-name"; + public static final String GUI_BUTTON_LEFT = "gui.buttons.left-name"; + public static final String GUI_BUTTON_EXIT = "gui.buttons.exit-name"; + + /** + * Retrieves a string from the config and translates alternate color codes (& -> §). + * + * @param config The plugin's configuration object. + * @param path The path in the config file. + * @param fallback The default value to return if the path is missing or null. + * @return A colorized string from the config or the provided fallback. + */ + public static String getWithColor(FileConfiguration config, String path, String fallback) { + if (config == null || path == null) return ChatColor.translateAlternateColorCodes('&', fallback); + String raw = config.getString(path, fallback); + return ChatColor.translateAlternateColorCodes('&', raw != null ? raw : fallback); } } + diff --git a/src/main/java/tokarotik/giftapi/Main.java b/src/main/java/tokarotik/giftapi/Main.java index 3a45ad6..35d34a9 100644 --- a/src/main/java/tokarotik/giftapi/Main.java +++ b/src/main/java/tokarotik/giftapi/Main.java @@ -9,63 +9,81 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import tokarotik.giftapi.cache.CacheManager; -import tokarotik.giftapi.dev.AddCommand; -import tokarotik.giftapi.dev.GiftCommand; -public class Main extends JavaPlugin implements Listener -{ +public class Main extends JavaPlugin implements Listener { + + private static final int INVENTORY_SLOTS = 54; + private static final int CACHED_SLOTS = INVENTORY_SLOTS - 9; + private CacheManager cacheManager; private APIManager apiManager; - private final int inventory_slots = 54; @Override - public void onEnable() - { + public void onEnable() { saveDefaultConfig(); - cacheManager = new CacheManager(inventory_slots - 9); - apiManager = new APIManager(this, getConfig(), inventory_slots); + this.cacheManager = new CacheManager(CACHED_SLOTS); + this.apiManager = new APIManager(this, getConfig(), INVENTORY_SLOTS); - this.getCommand("gift").setExecutor(new GiftCommand(this)); // DELETE FOR RELEASE - this.getCommand("add").setExecutor(new AddCommand(this)); // DELETE FOR RELEASE + registerEvents(); - getServer().getPluginManager().registerEvents(this, this); // bullshit - - getLogger().info("GiftAPI enabled!"); + getLogger().info("GiftAPI has been enabled."); } @Override - public void onDisable() - { - getLogger().info("Saving GiftApi..."); - cacheManager.disable(); + public void onDisable() { + getLogger().info("Saving GiftAPI state..."); + if (cacheManager != null) { + cacheManager.disable(); + } + getLogger().info("GiftAPI has been disabled."); + } - getLogger().info("GiftAPI disabled!"); + private void registerEvents() { + getServer().getPluginManager().registerEvents(this, this); } @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) - { - Bukkit.getScheduler().runTaskAsynchronously(this, () -> cacheManager.playerQuit(event.getPlayer())); + public void onPlayerQuit(PlayerQuitEvent event) { + final Player player = event.getPlayer(); + Bukkit.getScheduler().runTaskAsynchronously(this, () -> cacheManager.playerQuit(player)); } - // if failed to add, return false - // if successfully added, return true + /** + * Adds an item to the player's gift cache. + * This operation is asynchronous. + * + * @param player the player to add the item to + * @param item the item to be added + */ public void add(Player player, ItemStack item) { + if (player == null || item == null) return; Bukkit.getScheduler().runTaskAsynchronously(this, () -> apiManager.add(player, item)); } - // if failed to remove item, return false - // if successfully removed item, return true - public void remove(Player player, ItemStack item) - { + + /** + * Removes an item from the player's gift cache. + * This operation is asynchronous. + * + * @param player the player to remove the item from + * @param item the item to be removed + */ + public void remove(Player player, ItemStack item) { + if (player == null || item == null) return; Bukkit.getScheduler().runTaskAsynchronously(this, () -> apiManager.remove(player, item)); } - // will open inventory GiftAPI - public void openGUI(Player player) - { - Bukkit.getScheduler().runTaskAsynchronously(this, () -> apiManager.openInventory(player)); + /** + * Opens the GiftAPI GUI for the specified player. + * + * @param player the player for whom to open the GUI + */ + public void openGUI(Player player) { + if (player == null) return; + Bukkit.getScheduler().runTask(this, () -> apiManager.openInventory(player)); } - public CacheManager getCacheManager() { return this.cacheManager; } -} \ No newline at end of file + public CacheManager getCacheManager() { + return cacheManager; + } +} diff --git a/src/main/java/tokarotik/giftapi/NBT/item/ItemLoad.java b/src/main/java/tokarotik/giftapi/NBT/item/ItemLoad.java index 4fe314a..6e72e5e 100644 --- a/src/main/java/tokarotik/giftapi/NBT/item/ItemLoad.java +++ b/src/main/java/tokarotik/giftapi/NBT/item/ItemLoad.java @@ -1,98 +1,105 @@ package tokarotik.giftapi.NBT.item; +import net.minecraft.server.v1_6_R3.NBTTagCompound; import net.minecraft.server.v1_6_R3.NBTTagList; import net.minecraft.server.v1_6_R3.NBTTagString; -import net.minecraft.server.v1_6_R3.NBTTagCompound; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import java.util.Arrays; +import java.util.Collections; import java.util.List; -public class ItemLoad -{ - public static ItemStack getItem(NBTTagCompound tag) - { +public final class ItemLoad { - ItemStack item = new ItemStack( - getId(tag), getCount(tag), getDurability(tag) - ); + private ItemLoad() { + // Utility class; prevent instantiation + } - // тут чехорда. с начало сет айтем, после сет мета, а в самом конце гет мета - item.setItemMeta( - setMeta( - tag, - item.getItemMeta() - )); + /** + * Constructs an ItemStack from the given NBTTagCompound. + * + * @param tag the NBTTagCompound containing item data + * @return an ItemStack representing the item data from NBT + */ + public static ItemStack getItem(NBTTagCompound tag) { + int id = getId(tag); + short count = getCount(tag); + short durability = getDurability(tag); + + ItemStack item = new ItemStack(id, count, durability); + + // Set item meta after item creation + ItemMeta meta = item.getItemMeta(); + if (meta != null) { + meta = applyMeta(tag, meta); + item.setItemMeta(meta); + } return item; } - private static int getId(NBTTagCompound tag) - { - int id = 0; - if (tag.hasKey("id")) - { - id = tag.getInt("id"); - } - return id; + private static int getId(NBTTagCompound tag) { + return tag.hasKey("id") ? tag.getInt("id") : 0; } - private static short getCount(NBTTagCompound tag) - { - short count = 1; - if (tag.hasKey("count")) - { - count = tag.getShort("count"); - } - return count; + private static short getCount(NBTTagCompound tag) { + // Default count 1 for valid items + return tag.hasKey("count") ? tag.getShort("count") : 1; } - private static short getDurability(NBTTagCompound tag) - { - short durability = 0; - if (tag.hasKey("damage")) - { - durability = tag.getShort("damage"); - } - return durability; + private static short getDurability(NBTTagCompound tag) { + // Default durability 0 (undamaged) + return tag.hasKey("damage") ? tag.getShort("damage") : 0; } - private static ItemMeta setMeta(NBTTagCompound tag, ItemMeta meta) - { - if (tag.hasKey("meta")) - { + /** + * Applies metadata to an ItemMeta from the NBT tag. + * + * @param tag the NBTTagCompound containing meta data + * @param meta the ItemMeta to apply changes to + * @return the modified ItemMeta + */ + private static ItemMeta applyMeta(NBTTagCompound tag, ItemMeta meta) { + if (tag.hasKey("meta")) { NBTTagCompound metaNBT = tag.getCompound("meta"); - if (metaNBT.hasKey("name")) - { + if (metaNBT.hasKey("name")) { meta.setDisplayName(metaNBT.getString("name")); } - if (metaNBT.hasKey("lore")) - { - meta.setLore( - NBTListToStringList( - metaNBT.getList("lore") - ) - ); + + if (metaNBT.hasKey("lore")) { + NBTTagList loreList = metaNBT.getList("lore"); + List lore = nbtListToStringList(loreList); + meta.setLore(lore); } } - return meta; } - private static List NBTListToStringList(NBTTagList nbt) - { - String[] list = new String[nbt.size()]; - - for (int i = 0; i < nbt.size(); i++) - { - NBTTagString base = (NBTTagString) nbt.get(i); - - list[i] = base.data; + /** + * Converts an NBTTagList of strings to a List. + * + * @param nbt the NBTTagList containing NBTTagStrings + * @return a List of Strings extracted from the NBT list + */ + private static List nbtListToStringList(NBTTagList nbt) { + if (nbt == null || nbt.size() == 0) { + return Collections.emptyList(); } + String[] list = new String[nbt.size()]; + for (int i = 0; i < nbt.size(); i++) { + // Defensive cast, check instance before casting if possible + if (nbt.get(i) instanceof NBTTagString) { + NBTTagString base = (NBTTagString) nbt.get(i); + list[i] = base.data != null ? base.data : ""; + } else { + list[i] = ""; + } + } return Arrays.asList(list); } } + diff --git a/src/main/java/tokarotik/giftapi/NBT/item/ItemNBT.java b/src/main/java/tokarotik/giftapi/NBT/item/ItemNBT.java index 33404bc..bd8b40d 100644 --- a/src/main/java/tokarotik/giftapi/NBT/item/ItemNBT.java +++ b/src/main/java/tokarotik/giftapi/NBT/item/ItemNBT.java @@ -10,12 +10,24 @@ import org.bukkit.inventory.meta.ItemMeta; import java.util.List; import java.util.Map; +import java.util.Objects; -public class ItemNBT -{ - public static NBTTagCompound getTag(ItemStack item) - { - if (item == null) {return null;} +public final class ItemNBT { + + private ItemNBT() { + // Utility class; prevent instantiation + } + + /** + * Converts a Bukkit ItemStack into an NBTTagCompound representing the item. + * + * @param item the ItemStack to convert + * @return the NBTTagCompound representing the item or null if the item is null + */ + public static NBTTagCompound getTag(ItemStack item) { + if (item == null) { + return null; + } NBTTagCompound tag = new NBTTagCompound("item"); @@ -25,87 +37,78 @@ public class ItemNBT return tag; } - private static void setBasic(ItemStack item, NBTTagCompound tag) - { - short count = (short)item.getAmount(); - if (count != 1) { tag.setShort("count", count); } + private static void setBasic(ItemStack item, NBTTagCompound tag) { + short count = (short) item.getAmount(); + if (count != 1) { + tag.setShort("count", count); + } int id = item.getTypeId(); - if (id != 0) { tag.setInt("id", item.getTypeId()); } + if (id != 0) { + tag.setInt("id", id); + } short damage = item.getDurability(); - if (damage != 0) { tag.setShort("damage", item.getDurability()); } - } - - private static void setMetaTag(ItemStack item, NBTTagCompound baseTag) - { - NBTTagCompound tag = new NBTTagCompound(); - - if (item.hasItemMeta()) - { - ItemMeta meta = item.getItemMeta(); - - if (meta.hasDisplayName()) - { - tag.setString( - "name", - meta.getDisplayName() - ); - } - - if (meta.hasLore()) - { - tag.set( - "lore", - stringListToNBTCompound(meta.getLore()) - ); - } - - if (meta.hasEnchants()) - { - - tag.setCompound( - "enchantments", - enchantsToNBTCompound(meta.getEnchants()) - ); - } - - baseTag.setCompound("meta", tag); + if (damage != 0) { + tag.setShort("damage", damage); } } - private static NBTTagList stringListToNBTCompound(List list) - { - NBTTagList NBTlist = new NBTTagList(); - - for (String s : list) { - NBTlist.add( - new NBTTagString( - null, - s - ) - ); + private static void setMetaTag(ItemStack item, NBTTagCompound baseTag) { + if (!item.hasItemMeta()) { + return; + } + + ItemMeta meta = item.getItemMeta(); + NBTTagCompound metaTag = new NBTTagCompound(); + + if (meta.hasDisplayName()) { + metaTag.setString("name", meta.getDisplayName()); + } + + if (meta.hasLore()) { + metaTag.set("lore", stringListToNBTList(meta.getLore())); + } + + if (meta.hasEnchants()) { + metaTag.setCompound("enchantments", enchantsToNBTCompound(meta.getEnchants())); + } + + if (!metaTag.isEmpty()) { + baseTag.setCompound("meta", metaTag); } - return NBTlist; } - private static NBTTagCompound enchantsToNBTCompound(Map enchantments) - { - NBTTagCompound NBTlist = new NBTTagCompound(); + private static NBTTagList stringListToNBTList(List list) { + NBTTagList nbtList = new NBTTagList(); + if (list == null || list.isEmpty()) { + return nbtList; + } + for (String str : list) { + // Null-safe insertion of strings + nbtList.add(new NBTTagString(null, Objects.toString(str, ""))); + } + return nbtList; + } + + private static NBTTagCompound enchantsToNBTCompound(Map enchantments) { + NBTTagCompound enchantmentsNBT = new NBTTagCompound(); + + if (enchantments == null || enchantments.isEmpty()) { + return enchantmentsNBT; + } enchantments.forEach((enchantment, level) -> { + if (enchantment == null) return; // defensive check + NBTTagCompound enchantmentNBT = new NBTTagCompound(); + enchantmentNBT.setInt("level", level != null ? level : 0); - int levelNBT = 0; - if (level != null) // type Integer can be null. To not get a null error I wrote it :) - { - levelNBT = level; - } - enchantmentNBT.setInt("level", levelNBT); - - NBTlist.setCompound(String.valueOf(enchantment.getId()), enchantmentNBT); + enchantmentsNBT.setCompound(String.valueOf(enchantment.getId()), enchantmentNBT); }); - return NBTlist; + + return enchantmentsNBT; } } + diff --git a/src/main/java/tokarotik/giftapi/NBT/pages/PagesManager.java b/src/main/java/tokarotik/giftapi/NBT/pages/PagesManager.java index aa8c976..05773e0 100644 --- a/src/main/java/tokarotik/giftapi/NBT/pages/PagesManager.java +++ b/src/main/java/tokarotik/giftapi/NBT/pages/PagesManager.java @@ -1,56 +1,78 @@ package tokarotik.giftapi.NBT.pages; import net.minecraft.server.v1_6_R3.NBTTagList; - import org.bukkit.inventory.ItemStack; -public class PagesManager -{ - private NBTTagList tag; - private int current_page = 0; - private final int items_slots; - public PagesManager(NBTTagList tag, int items_slots) - { - this.items_slots = items_slots; - this.tag = tag; +public final class PagesManager { + + private volatile NBTTagList tag; + private volatile int currentPage = 0; + private final int itemsSlots; + + public PagesManager(NBTTagList tag, int itemsSlots) { + this.itemsSlots = itemsSlots; + this.tag = tag != null ? tag : new NBTTagList("giftapi"); } - public PagesManager(int items_slots) - { - this.items_slots = items_slots; + public PagesManager(int itemsSlots) { + this.itemsSlots = itemsSlots; this.tag = new NBTTagList("giftapi"); } - public synchronized void add(ItemStack item) - { + public synchronized void add(ItemStack item) { PagesUtil.add(item, this.tag); } - public synchronized void remove(ItemStack item) - { + public synchronized void remove(ItemStack item) { this.tag = PagesUtil.remove(item, this.tag); - } - - public synchronized NBTTagList getTag() { return this.tag; } - - public synchronized ItemStack[] getList() { return PagesUtil.getRawList(this.tag); } - - public synchronized int getCurrentPage() { return this.current_page; } - public synchronized void setCurrentPage(int current_page) { this.current_page = current_page; } - public int getCountPages() { return this.tag.size() / (this.items_slots + 1); } - public synchronized void nextPage() - { - if (getCountPages() > getCurrentPage()) - { - this.current_page++; + // Optionally reset currentPage if it's now out of range + int maxPage = getCountPages() - 1; + if (currentPage > maxPage) { + currentPage = Math.max(0, maxPage); } } - public synchronized void backPage() - { - if (0 < getCurrentPage()) - { - this.current_page -= 1; + public synchronized NBTTagList getTag() { + return tag; + } + + public synchronized ItemStack[] getList() { + return PagesUtil.getRawList(tag); + } + + public synchronized int getCurrentPage() { + return currentPage; + } + + public synchronized void setCurrentPage(int currentPage) { + if (currentPage < 0) { + this.currentPage = 0; + } else if (currentPage >= getCountPages()) { + this.currentPage = Math.max(0, getCountPages() - 1); + } else { + this.currentPage = currentPage; + } + } + + /** + * Gets the total number of pages, accounting for partial pages. + */ + public synchronized int getCountPages() { + int size = tag.size(); + if (size == 0) return 1; + return (int) Math.ceil((double) size / itemsSlots); + } + + public synchronized void nextPage() { + if (currentPage < getCountPages() - 1) { + currentPage++; + } + } + + public synchronized void backPage() { + if (currentPage > 0) { + currentPage--; } } } + diff --git a/src/main/java/tokarotik/giftapi/NBT/pages/PagesUtil.java b/src/main/java/tokarotik/giftapi/NBT/pages/PagesUtil.java index 80b6ccc..9577825 100644 --- a/src/main/java/tokarotik/giftapi/NBT/pages/PagesUtil.java +++ b/src/main/java/tokarotik/giftapi/NBT/pages/PagesUtil.java @@ -2,69 +2,85 @@ package tokarotik.giftapi.NBT.pages; import net.minecraft.server.v1_6_R3.NBTTagCompound; import net.minecraft.server.v1_6_R3.NBTTagList; - import org.bukkit.inventory.ItemStack; - import tokarotik.giftapi.NBT.item.ItemLoad; import tokarotik.giftapi.NBT.item.ItemNBT; -public class PagesUtil -{ - public synchronized static void add(ItemStack item, NBTTagList tag) - { +import java.util.ArrayList; +import java.util.List; + +public final class PagesUtil { + + private PagesUtil() { + // Utility class - prevent instantiation + } + + public static synchronized void add(ItemStack item, NBTTagList tag) { + if (item == null || tag == null) return; + NBTTagCompound nbt = ItemNBT.getTag(item); - - tag.add(nbt); + if (nbt != null) { + tag.add(nbt); + } } - public synchronized static NBTTagList remove(ItemStack item, NBTTagList tag) - { - ItemStack[] new_list = addAllExtraOne(tag, item); - return itemStackListToNBTList(new_list); - } + public static synchronized NBTTagList remove(ItemStack item, NBTTagList tag) { + if (item == null || tag == null) return tag; - public synchronized static ItemStack[] addAllExtraOne(NBTTagList tag, ItemStack item) - { - ItemStack[] new_list = new ItemStack[tag.size()]; - ItemStack[] old_list = getRawList(tag); + // Convert NBTTagList -> List + List oldList = getListFromNBT(tag); + List newList = new ArrayList<>(oldList.size()); - for (int i = 0;i < tag.size();i++) - { - if (!old_list[i].equals(item)) - { - new_list[i] = old_list[i]; + for (ItemStack stack : oldList) { + if (!equalsItem(stack, item)) { + newList.add(stack); } } - return new_list; + return itemStackListToNBTList(newList); } - public synchronized static NBTTagList itemStackListToNBTList(ItemStack[] items) - { + private static boolean equalsItem(ItemStack a, ItemStack b) { + if (a == null || b == null) return false; + return a.isSimilar(b); + } + + public static synchronized NBTTagList itemStackListToNBTList(List items) { NBTTagList list = new NBTTagList(); for (ItemStack item : items) { if (item != null) { NBTTagCompound compound = ItemNBT.getTag(item); - - list.add(compound); + if (compound != null) { + list.add(compound); + } } } return list; } - public synchronized static ItemStack[] getRawList(NBTTagList tag) - { + public static synchronized ItemStack[] getRawList(NBTTagList tag) { + if (tag == null) return new ItemStack[0]; + ItemStack[] list = new ItemStack[tag.size()]; - for (int i = 0; i < tag.size(); i++) - { + for (int i = 0; i < tag.size(); i++) { NBTTagCompound compound = (NBTTagCompound) tag.get(i); - list[i] = ItemLoad.getItem(compound); } return list; } + + private static synchronized List getListFromNBT(NBTTagList tag) { + ItemStack[] array = getRawList(tag); + List list = new ArrayList<>(array.length); + for (ItemStack item : array) { + if (item != null) { + list.add(item); + } + } + return list; + } } diff --git a/src/main/java/tokarotik/giftapi/NBT/savers/BasicNBT.java b/src/main/java/tokarotik/giftapi/NBT/savers/BasicNBT.java index 3e62805..716fdb2 100644 --- a/src/main/java/tokarotik/giftapi/NBT/savers/BasicNBT.java +++ b/src/main/java/tokarotik/giftapi/NBT/savers/BasicNBT.java @@ -9,85 +9,127 @@ import org.bukkit.craftbukkit.v1_6_R3.entity.CraftPlayer; import org.bukkit.entity.Player; import java.io.File; -import java.io.FileOutputStream; import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.logging.Level; -public class BasicNBT { +public final class BasicNBT { - private final String path; + private final Path worldPath; - public BasicNBT(String world) - { - this.path = getPath() + "\\" + world; + public BasicNBT(String world) { + this.worldPath = Paths.get(getBasePath(), world); } - public boolean writePlayerNBT(NBTTagCompound compound, EntityPlayer entityPlayer) - { - try - { - entityPlayer.f(compound); - - FileOutputStream out = new FileOutputStream(getSavePath(entityPlayer)); - NBTCompressedStreamTools.a(compound, out); - out.close(); - } - catch(Exception e) - { - Bukkit.getLogger().warning(e.toString()); - + /** + * Writes the player's NBT data to disk. + * + * @param compound the NBTTagCompound representing player data + * @param entityPlayer the player entity + * @return true if write succeeded, false otherwise + */ + public boolean writePlayerNBT(NBTTagCompound compound, EntityPlayer entityPlayer) { + if (compound == null || entityPlayer == null) { + Bukkit.getLogger().warning("Attempted to write NBT with null compound or player"); + return false; + } + + // Update NBT with entityPlayer state before saving + entityPlayer.f(compound); + + Path savePath = getSavePath(entityPlayer); + File saveFile = savePath.toFile(); + + // Ensure parent directories exist + File parentDir = saveFile.getParentFile(); + if (parentDir != null && !parentDir.exists() && !parentDir.mkdirs()) { + Bukkit.getLogger().warning("Failed to create directories for player save path: " + parentDir); + return false; + } + + try (FileOutputStream out = new FileOutputStream(saveFile)) { + NBTCompressedStreamTools.a(compound, out); + return true; + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to write player NBT for " + entityPlayer.getName(), e); return false; } - return true; } - public NBTTagCompound readPlayerNBT(EntityPlayer entityPlayer) - { - try - { - FileInputStream in = new FileInputStream(getSavePath(entityPlayer)); + /** + * Reads the player's NBT data from disk. + * If the file does not exist or fails to read, returns a fresh NBTTagCompound + * initialized with the player's current state. + * + * @param entityPlayer the player entity + * @return the player's NBTTagCompound data or null if unrecoverable error occurs + */ + public NBTTagCompound readPlayerNBT(EntityPlayer entityPlayer) { + if (entityPlayer == null) { + Bukkit.getLogger().warning("Attempted to read NBT for null player"); + return null; + } + + Path savePath = getSavePath(entityPlayer); + File saveFile = savePath.toFile(); + + if (!saveFile.exists()) { + // No existing data, return fresh compound from entity state + return getFreshPlayerNBT(entityPlayer); + } + + try (FileInputStream in = new FileInputStream(saveFile)) { NBTTagCompound compound = NBTCompressedStreamTools.a(in); - + // Load data into entityPlayer entityPlayer.e(compound); - return compound; - } - catch(Exception ignored) - { - try - { - NBTTagCompound compound = new NBTTagCompound(); - entityPlayer.e(compound); - - return compound; - } - - catch (Exception e) - { - Bukkit.getLogger().warning(e.toString()); - return null; - } + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to read player NBT for " + entityPlayer.getName(), e); + return getFreshPlayerNBT(entityPlayer); } } - public static EntityPlayer getPlayer(Player player) - { - return ((CraftPlayer) player).getHandle(); - } - - public String getPath() { - try - { - return new File(".").getCanonicalFile().getAbsolutePath(); - } - - catch (Exception e) - { + private NBTTagCompound getFreshPlayerNBT(EntityPlayer entityPlayer) { + try { + NBTTagCompound compound = new NBTTagCompound(); + entityPlayer.e(compound); + return compound; + } catch (Exception e) { + Bukkit.getLogger().log(Level.SEVERE, "Failed to initialize fresh NBT for " + entityPlayer.getName(), e); return null; } } - private String getSavePath(EntityPlayer entityPlayer) - { - return this.path + "\\players\\" + entityPlayer.getName() + ".dat"; + public static EntityPlayer getPlayer(Player player) { + if (!(player instanceof CraftPlayer)) { + throw new IllegalArgumentException("Player must be a CraftPlayer instance"); + } + return ((CraftPlayer) player).getHandle(); + } + + /** + * Returns the base directory path of the running server jar or working directory. + */ + private String getBasePath() { + try { + return new File(".").getCanonicalPath(); + } catch (IOException e) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get base path", e); + return System.getProperty("user.dir"); // fallback + } + } + + /** + * Constructs the file path where the player's NBT data should be saved. + * + * @param entityPlayer the player entity + * @return Path to the player's .dat file + */ + private Path getSavePath(EntityPlayer entityPlayer) { + return worldPath.resolve(Paths.get("players", entityPlayer.getName() + ".dat")); } } + diff --git a/src/main/java/tokarotik/giftapi/NBT/savers/WorldNBTSynchronizer.java b/src/main/java/tokarotik/giftapi/NBT/savers/WorldNBTSynchronizer.java index 4e5f11a..2c7ddb3 100644 --- a/src/main/java/tokarotik/giftapi/NBT/savers/WorldNBTSynchronizer.java +++ b/src/main/java/tokarotik/giftapi/NBT/savers/WorldNBTSynchronizer.java @@ -5,20 +5,26 @@ import net.minecraft.server.v1_6_R3.NBTTagCompound; import org.bukkit.Bukkit; import org.bukkit.World; -import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; -public class WorldNBTSynchronizer -{ - @Nullable - public static String getWorldWhatHasPlayer(EntityPlayer entityPlayer) - { - for (World world : Bukkit.getWorlds()) - { - String worldName = world.getName(); +import java.util.Objects; - if (hasAll(entityPlayer, worldName)) - { +public final class WorldNBTSynchronizer { + + /** + * Finds the name of the world that contains the given player and where the player's NBT + * has a valid "giftapi" tag. + * + * @param entityPlayer the player entity to check + * @return the world name if found, otherwise null + */ + @Nullable + public static String getWorldContainingPlayerWithGiftApiNBT(EntityPlayer entityPlayer) { + Objects.requireNonNull(entityPlayer, "entityPlayer cannot be null"); + + for (World world : Bukkit.getWorlds()) { + String worldName = world.getName(); + if (isPlayerInWorldWithGiftApiNBT(entityPlayer, worldName)) { return worldName; } } @@ -26,47 +32,62 @@ public class WorldNBTSynchronizer return null; } - public static boolean hasAll(EntityPlayer entityPlayer, String worldName) - { - boolean isHasPlayer = hasPlayer(entityPlayer.getName(), worldName); - if (!isHasPlayer) { return false; } - - return hasGiftApiNBT(entityPlayer, worldName); + /** + * Checks whether the player with the specified name exists in the world and + * if their NBT contains the "giftapi" tag. + * + * @param entityPlayer the player entity + * @param worldName the world name + * @return true if player exists in world and has valid giftapi NBT, false otherwise + */ + public static boolean isPlayerInWorldWithGiftApiNBT(EntityPlayer entityPlayer, String worldName) { + if (!isPlayerOnlineInWorld(entityPlayer.getName(), worldName)) { + return false; + } + return doesPlayerNBTContainGiftApi(entityPlayer, worldName); } - private static boolean hasPlayer(String playerName, String worldName) - { + /** + * Checks if the player with the given name is currently online in the specified world. + * + * @param playerName the player name to check + * @param worldName the world name + * @return true if player is online in the world, false otherwise + */ + private static boolean isPlayerOnlineInWorld(String playerName, String worldName) { World world = Bukkit.getWorld(worldName); - - for (Player player : world.getPlayers()) - { - if (player.getName().equals(playerName)) { return true; } + if (world == null) { + return false; } - return false; + // Use stream for clarity and short-circuiting + return world.getPlayers().stream() + .anyMatch(player -> player.getName().equals(playerName)); } - private static boolean hasGiftApiNBT(EntityPlayer entityPlayer, String worldName) - { + /** + * Checks if the player's NBT data in the specified world contains a valid "giftapi" NBTTagList. + * + * @param entityPlayer the player entity + * @param worldName the world name + * @return true if valid giftapi tag exists, false otherwise + */ + private static boolean doesPlayerNBTContainGiftApi(EntityPlayer entityPlayer, String worldName) { BasicNBT basicNBT = new BasicNBT(worldName); - NBTTagCompound tag = basicNBT.readPlayerNBT(entityPlayer); - if (tag == null) { return false; } - - if (tag.hasKey("giftapi")) // if exists giftapi tag in player - { - try - { - tag.getList("giftapi"); - return true; - } - - catch(Exception ignored){} // if giftapi is not type list, throw exception and for what I used construction try-catch. + if (tag == null || !tag.hasKey("giftapi")) { + return false; } - return false; + try { + // Check if "giftapi" is an NBTTagList + tag.getList("giftapi"); + return true; + } catch (Exception e) { + // Swallow exception since the tag exists but is invalid type + return false; + } } - - } + diff --git a/src/main/java/tokarotik/giftapi/cache/CacheManager.java b/src/main/java/tokarotik/giftapi/cache/CacheManager.java index cefb651..9b2643a 100644 --- a/src/main/java/tokarotik/giftapi/cache/CacheManager.java +++ b/src/main/java/tokarotik/giftapi/cache/CacheManager.java @@ -1,61 +1,97 @@ package tokarotik.giftapi.cache; import net.minecraft.server.v1_6_R3.EntityPlayer; - import org.bukkit.entity.Player; - import tokarotik.giftapi.NBT.savers.BasicNBT; import tokarotik.giftapi.NBT.savers.WorldNBTSynchronizer; import tokarotik.giftapi.NBT.pages.PagesManager; import java.util.Map; -public class CacheManager -{ +/** + * Manages player-specific cache of PagesManager instances across worlds. + * Handles loading from NBT or initializing defaults. + */ +public class CacheManager { + private final CacheUtil cacheUtil; - private final int inventory_slots; + private final int inventorySlots; - public CacheManager(int inventory_slots) - { - this.inventory_slots = inventory_slots; - cacheUtil = new CacheUtil(inventory_slots); + public CacheManager(int inventorySlots) { + if (inventorySlots <= 0) { + throw new IllegalArgumentException("Inventory slots must be positive."); + } + + this.inventorySlots = inventorySlots; + this.cacheUtil = new CacheUtil(inventorySlots); } - public synchronized PagesManager load(Player player) { - String playerWorld = player.getWorld().getName(); - Map worldMap = cacheUtil.getWorlMap(playerWorld); - EntityPlayer entityPlayer = BasicNBT.getPlayer(player); - - // Return cached PageManager if already loaded - if (worldMap.containsKey(player)) { - return worldMap.get(player); + /** + * Loads the PagesManager for a given player. + * If already cached, returns immediately. + * Attempts to load from NBT, falling back to default if necessary. + * + * @param player the player to load data for + * @return PagesManager instance associated with the player + */ + public PagesManager load(Player player) { + if (player == null) { + throw new IllegalArgumentException("Player cannot be null."); } - // Attempt to load from current world - if (WorldNBTSynchronizer.hasAll(entityPlayer, playerWorld)) { - return this.cacheUtil.loadAndCachePageManager(player, playerWorld, entityPlayer, worldMap); + synchronized (getLockFor(player)) { + String worldName = player.getWorld().getName(); + EntityPlayer entityPlayer = BasicNBT.getPlayer(player); + Map worldMap = cacheUtil.getWorlMap(worldName); + + // Use cached instance if present + if (worldMap.containsKey(player)) { + return worldMap.get(player); + } + + // Load from NBT if available in current or alternate world + if (WorldNBTSynchronizer.isPlayerInWorldWithGiftApiNBT(entityPlayer, worldName)) { + return cacheUtil.loadAndCachePageManager(player, worldName, entityPlayer, worldMap); + } + + String alternateWorld = WorldNBTSynchronizer.getWorldContainingPlayerWithGiftApiNBT(entityPlayer); + if (alternateWorld != null) { + return cacheUtil.loadAndCachePageManager(player, alternateWorld, entityPlayer, worldMap); + } + + // Default fallback: new empty manager + PagesManager defaultPages = new PagesManager(inventorySlots); + worldMap.put(player, defaultPages); + return defaultPages; } - - // Attempt to load from another world if present - String alternateWorld = WorldNBTSynchronizer.getWorldWhatHasPlayer(entityPlayer); - if (alternateWorld != null) { - return this.cacheUtil.loadAndCachePageManager(player, alternateWorld, entityPlayer, worldMap); - } - - // Fallback to new empty PageManager - PagesManager defaultPages = new PagesManager(this.inventory_slots); - worldMap.put(player, defaultPages); - - return defaultPages; } - public synchronized void disable() - { + /** + * Saves all cached player data to disk. + * Call during plugin disable. + */ + public void disable() { cacheUtil.saveAllHard(); } - public synchronized void playerQuit(Player player) - { - this.cacheUtil.saveHard(player); + /** + * Saves data for a player when they leave. + * + * @param player the player who quit + */ + public void playerQuit(Player player) { + if (player == null) return; + + synchronized (getLockFor(player)) { + cacheUtil.saveHard(player); + } + } + + /** + * Simple locking strategy for per-player concurrency without coarse-grained locking. + */ + private Object getLockFor(Player player) { + // Optional improvement: use a WeakHashMap for real-world concurrency handling + return player.getUniqueId(); } } diff --git a/src/main/java/tokarotik/giftapi/cache/CacheUtil.java b/src/main/java/tokarotik/giftapi/cache/CacheUtil.java index 0a7e389..8ffc5af 100644 --- a/src/main/java/tokarotik/giftapi/cache/CacheUtil.java +++ b/src/main/java/tokarotik/giftapi/cache/CacheUtil.java @@ -3,80 +3,119 @@ package tokarotik.giftapi.cache; import net.minecraft.server.v1_6_R3.EntityPlayer; import net.minecraft.server.v1_6_R3.NBTTagCompound; import net.minecraft.server.v1_6_R3.NBTTagList; - import org.bukkit.Bukkit; -import org.bukkit.World; import org.bukkit.entity.Player; - import tokarotik.giftapi.NBT.savers.BasicNBT; import tokarotik.giftapi.NBT.pages.PagesManager; -import java.util.HashMap; -import java.util.Map; +import java.util.*; -public class CacheUtil -{ - public final Map world_map = new HashMap<>(); - public final Map nether_map = new HashMap<>(); - public final Map end_map = new HashMap<>(); +/** + * Handles caching and persistent storage of PagesManager instances + * for players across different world environments. + */ +public class CacheUtil { - public final int inventory_slots; + private static final String OVERWORLD = "world"; + private static final String NETHER = "world_nether"; + private static final String END = "world_the_end"; - public CacheUtil(int inventory_slots) - { - this.inventory_slots = inventory_slots; + private final Map overworldCache = new HashMap<>(); + private final Map netherCache = new HashMap<>(); + private final Map endCache = new HashMap<>(); + + private final int inventorySlots; + + public CacheUtil(int inventorySlots) { + if (inventorySlots <= 0) { + throw new IllegalArgumentException("Inventory slots must be greater than zero."); + } + this.inventorySlots = inventorySlots; } - public synchronized void saveHard(Player player) - { - for (World world : Bukkit.getWorlds()) - { - String nameWorld = world.getName(); - Map map = this.getWorlMap(nameWorld); + /** + * Saves the player's data across all three known dimensions. + * + * @param player the player whose data to save + */ + public synchronized void saveHard(Player player) { + if (player == null) return; - if (map == null) { continue; } + EntityPlayer entityPlayer = BasicNBT.getPlayer(player); - PagesManager pages = map.get(player); + for (Map.Entry> entry : getWorldCaches().entrySet()) { + String worldName = entry.getKey(); + Map cache = entry.getValue(); - if (pages == null) { continue; } + PagesManager pages = cache.get(player); + if (pages == null) continue; NBTTagList tag = pages.getTag(); - EntityPlayer entityPlayer = BasicNBT.getPlayer(player); - - BasicNBT basicNBT = new BasicNBT(nameWorld); - + BasicNBT basicNBT = new BasicNBT(worldName); NBTTagCompound compound = basicNBT.readPlayerNBT(entityPlayer); + compound.set("giftapi", tag); basicNBT.writePlayerNBT(compound, entityPlayer); } } - public synchronized void saveAllHard() - { - for (Player player : Bukkit.getOnlinePlayers()) - { + /** + * Saves all player data in all world caches. + */ + public synchronized void saveAllHard() { + for (Player player : Bukkit.getOnlinePlayers()) { saveHard(player); } } - public Map getWorlMap(String worldName) - { - switch(worldName) - { - case "world": { return this.world_map; } - case "world_nether": { return this.nether_map; } - case "world_the_end": { return this.end_map; } + /** + * Retrieves the proper world-specific player cache map. + * + * @param worldName name of the world + * @return the map of players and their PagesManager for that world + */ + public Map getWorlMap(String worldName) { + switch (worldName) { + case OVERWORLD: + return overworldCache; + case NETHER: + return netherCache; + case END: + return endCache; + default: + return null; } - - return null; } + /** + * Loads and caches a PagesManager instance from NBT data for the given player. + * + * @param player the player + * @param world the world name + * @param entityPlayer the NMS player instance + * @param worldMap the cache map where the data will be stored + * @return loaded and cached PagesManager instance + */ public synchronized PagesManager loadAndCachePageManager(Player player, String world, EntityPlayer entityPlayer, Map worldMap) { NBTTagCompound compound = new BasicNBT(world).readPlayerNBT(entityPlayer); NBTTagList giftList = compound.getList("giftapi"); + PagesManager pagesManager = new PagesManager(giftList, inventorySlots); - PagesManager pagesManager = new PagesManager(giftList, this.inventory_slots); worldMap.put(player, pagesManager); return pagesManager; } + + /** + * Utility to aggregate all internal world caches. + * + * @return map of worldName -> (player, pagesManager map) + */ + private Map> getWorldCaches() { + Map> worldCaches = new HashMap<>(); + worldCaches.put(OVERWORLD, overworldCache); + worldCaches.put(NETHER, netherCache); + worldCaches.put(END, endCache); + return worldCaches; + } } + diff --git a/src/main/java/tokarotik/giftapi/dev/AddCommand.java b/src/main/java/tokarotik/giftapi/dev/AddCommand.java deleted file mode 100644 index 51aa64a..0000000 --- a/src/main/java/tokarotik/giftapi/dev/AddCommand.java +++ /dev/null @@ -1,54 +0,0 @@ -package tokarotik.giftapi.dev; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; - -import tokarotik.giftapi.Main; - -import java.util.Random; -import java.util.concurrent.ThreadLocalRandom; - - -public class AddCommand implements CommandExecutor { - private final Main plugin; - - public AddCommand(Main plugin) - { - this.plugin = plugin; - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String s, String[] strings) { - - Player player; - - if (sender == null) { - if (Bukkit.getOnlinePlayers().length == 0) { return true; } - player = Bukkit.getOnlinePlayers()[0]; - } - else { - player = (Player) sender; - } - - int count = 1; - if (strings.length > 0) - { - count = Integer.parseInt(strings[0]); - } - - for (int i = 0; i < count; i++) { - Material[] values = Material.values(); - int id = values[ThreadLocalRandom.current().nextInt(values.length)].getId(); - ItemStack item = new ItemStack(id); - - this.plugin.add(player, item); - } - - return true; - } -} \ No newline at end of file diff --git a/src/main/java/tokarotik/giftapi/dev/GiftCommand.java b/src/main/java/tokarotik/giftapi/dev/GiftCommand.java deleted file mode 100644 index 8ced699..0000000 --- a/src/main/java/tokarotik/giftapi/dev/GiftCommand.java +++ /dev/null @@ -1,43 +0,0 @@ -package tokarotik.giftapi.dev; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -import tokarotik.giftapi.APIManager; -import tokarotik.giftapi.Main; - -public class GiftCommand implements CommandExecutor { - - private final Main plugin; - - public GiftCommand(Main plugin) - { - - this.plugin = plugin; - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String s, String[] strings) { - Player player; - - if (!(sender instanceof Player)) - { - sender.sendMessage("This command can use only player!"); - player = Bukkit.getOnlinePlayers()[0]; - } - - else - { - player = (Player) sender; - } - - this.plugin.openGUI(player); - - - return true; - } -} - diff --git a/src/main/java/tokarotik/giftapi/inventory/InventoryManager.java b/src/main/java/tokarotik/giftapi/inventory/InventoryManager.java index 00cba5f..0c8e692 100644 --- a/src/main/java/tokarotik/giftapi/inventory/InventoryManager.java +++ b/src/main/java/tokarotik/giftapi/inventory/InventoryManager.java @@ -7,53 +7,92 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.EventHandler; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; - import tokarotik.giftapi.cache.CacheManager; import tokarotik.giftapi.NBT.pages.PagesManager; +/** + * Manages interactions with the custom inventory GUI, including + * rendering, handling clicks, and modifying player item pages. + */ +public class InventoryManager implements Listener { -public class InventoryManager implements Listener -{ - private final String nameGUI; - private final int inventory_slots; + private final String guiTitle; + private final int inventorySlots; private final CacheManager cacheManager; private final InventoryUtil inventoryUtil; - - public InventoryManager(CacheManager cacheManager, int inventory_slots, String nameGUI, String nameArrowRight, String nameArrowLeft, String nameExit) - { - this.nameGUI = nameGUI; - this.inventory_slots = inventory_slots; + /** + * Constructs an InventoryManager responsible for handling a custom inventory UI. + * + * @param cacheManager player cache handler + * @param inventorySlots total number of inventory slots + * @param guiTitle title of the GUI + * @param arrowRight text for the right-arrow button + * @param arrowLeft text for the left-arrow button + * @param exitButton text for the exit button + */ + public InventoryManager(CacheManager cacheManager, int inventorySlots, String guiTitle, + String arrowRight, String arrowLeft, String exitButton) { + this.guiTitle = guiTitle; + this.inventorySlots = inventorySlots; this.cacheManager = cacheManager; - int items_slots = inventory_slots - 9; - inventoryUtil = new InventoryUtil(cacheManager, nameGUI, nameArrowRight, nameArrowLeft, nameExit, items_slots); + + int itemSlots = inventorySlots - 9; // reserve 9 for controls + this.inventoryUtil = new InventoryUtil(cacheManager, guiTitle, arrowRight, arrowLeft, exitButton, itemSlots); } + /** + * Handles player clicking within a managed custom inventory. + * + * @param event the inventory click event + */ @EventHandler - public synchronized void onInventoryClick(InventoryClickEvent event) - { - this.inventoryUtil.onInventoryClick(event); + public void handleInventoryClick(InventoryClickEvent event) { + inventoryUtil.onInventoryClick(event); } - public synchronized void add(Player player, ItemStack item) - { - PagesManager pages = this.cacheManager.load(player); + /** + * Adds an item to the player's PagesManager. + * + * @param player the target player + * @param item the item to add + */ + public void add(Player player, ItemStack item) { + if (player == null || item == null) return; + + PagesManager pages = cacheManager.load(player); pages.add(item); } - public synchronized void remove(Player player, ItemStack item) - { - PagesManager pages = this.cacheManager.load(player); + /** + * Removes an item from the player's PagesManager. + * + * @param player the target player + * @param item the item to remove + */ + public void remove(Player player, ItemStack item) { + if (player == null || item == null) return; + + PagesManager pages = cacheManager.load(player); pages.remove(item); } - public synchronized void openCustomInventory(Player player) - { - this.cacheManager.load(player).setCurrentPage(0); - Inventory inventory = Bukkit.createInventory(null, this.inventory_slots, this.nameGUI); + /** + * Opens the custom GUI for the specified player. + * Resets to the first page before rendering. + * + * @param player the player to open the GUI for + */ + public void openCustomInventory(Player player) { + if (player == null) return; - this.inventoryUtil.updateInventory(inventory, player); + PagesManager pages = cacheManager.load(player); + pages.setCurrentPage(0); + + Inventory inventory = Bukkit.createInventory(null, inventorySlots, guiTitle); + inventoryUtil.updateInventory(inventory, player); player.openInventory(inventory); } } + diff --git a/src/main/java/tokarotik/giftapi/inventory/InventoryUtil.java b/src/main/java/tokarotik/giftapi/inventory/InventoryUtil.java index 4784f48..1d484ac 100644 --- a/src/main/java/tokarotik/giftapi/inventory/InventoryUtil.java +++ b/src/main/java/tokarotik/giftapi/inventory/InventoryUtil.java @@ -12,124 +12,112 @@ import tokarotik.giftapi.NBT.pages.PagesManager; import tokarotik.giftapi.inventory.inventoryitems.InventoryItems; import tokarotik.giftapi.inventory.inventoryitems.InventoryItemsUtil; -public class InventoryUtil -{ +/** + * Utility class for managing the inventory GUI elements and user interaction. + */ +public class InventoryUtil { + private final CacheManager cacheManager; private final InventoryItems inventoryItems; - private final String nameGUI; - private final String nameArrowRight; - private final String nameArrowLeft; - private final String nameExit; - private final int inventory_slots; + private final String guiTitle; + private final String arrowRightLabel; + private final String arrowLeftLabel; + private final String exitLabel; + private final int inventorySlots; + + public InventoryUtil(CacheManager cacheManager, String guiTitle, + String arrowRightLabel, String arrowLeftLabel, + String exitLabel, int inventorySlots) { - public InventoryUtil(CacheManager cacheManager, String nameGUI, String nameArrowRight, String nameArrowLeft, String nameExit, int inventory_slots) - { this.cacheManager = cacheManager; - this.nameGUI = nameGUI; - this.nameArrowRight = nameArrowRight; - this.nameArrowLeft = nameArrowLeft; - this.nameExit = nameExit; - this.inventory_slots = inventory_slots; - this.inventoryItems = new InventoryItems(inventory_slots); + this.guiTitle = guiTitle; + this.arrowRightLabel = arrowRightLabel; + this.arrowLeftLabel = arrowLeftLabel; + this.exitLabel = exitLabel; + this.inventorySlots = inventorySlots; + this.inventoryItems = new InventoryItems(inventorySlots); } - public void onInventoryClick(InventoryClickEvent event) - { + /** + * Handles clicks in the custom GUI and controls pagination or exiting. + */ + public void onInventoryClick(InventoryClickEvent event) { Inventory inventory = event.getInventory(); + if (!event.getView().getTitle().equals(this.guiTitle)) return; - if (inventory.getTitle().equals(this.nameGUI)) { - event.setCancelled(true); + event.setCancelled(true); - ItemStack currentItem = event.getCurrentItem(); + ItemStack clickedItem = event.getCurrentItem(); + if (clickedItem == null || !clickedItem.hasItemMeta()) return; - if (currentItem == null) { return; } - String nameCurrentItem = null; - if (currentItem.hasItemMeta()) { - if (currentItem.getItemMeta().hasDisplayName()) { - nameCurrentItem = currentItem.getItemMeta().getDisplayName(); - } + String displayName = clickedItem.getItemMeta().getDisplayName(); + if (displayName == null) return; + + Player player = (Player) event.getWhoClicked(); + PagesManager pagesManager = cacheManager.load(player); + + if (displayName.equals(arrowRightLabel)) { + if (pagesManager.getCurrentPage() < pagesManager.getCountPages()) { + pagesManager.nextPage(); + updateInventory(inventory, player); } - Player player = (Player) event.getWhoClicked(); - - if (nameCurrentItem != null) - { - if (nameCurrentItem.equals(this.nameArrowRight)) { - PagesManager pagesManager = this.cacheManager.load(player); - - if (pagesManager.getCountPages() > pagesManager.getCurrentPage()) - { - pagesManager.nextPage(); - updateInventory(inventory, player); - } - } - - if (nameCurrentItem.equals(this.nameArrowLeft)) { - PagesManager pagesManager = this.cacheManager.load(player); - - if (0 < pagesManager.getCurrentPage()) - { - pagesManager.backPage(); - updateInventory(inventory, player); - } - } - - if (nameCurrentItem.equals(this.nameExit)) { - player.closeInventory(); - } + } else if (displayName.equals(arrowLeftLabel)) { + if (pagesManager.getCurrentPage() > 0) { + pagesManager.backPage(); + updateInventory(inventory, player); } + + } else if (displayName.equals(exitLabel)) { + player.closeInventory(); } } - public synchronized void updateInventory(Inventory inventory, Player player) - { + /** + * Refreshes the inventory GUI with items and controls for the current page. + */ + public void updateInventory(Inventory inventory, Player player) { inventory.clear(); - PagesManager pagesManager = this.cacheManager.load(player); + PagesManager pagesManager = cacheManager.load(player); - this.inventoryItems.renderPage(pagesManager, inventory); - setUI(inventory, pagesManager); + inventoryItems.renderPage(pagesManager, inventory); + renderControls(inventory, pagesManager); } - private synchronized void setUI(Inventory inventory, PagesManager pagesManager) - { - int height = (this.inventory_slots / 9); + /** + * Adds navigation and exit controls to the inventory UI. + */ + private void renderControls(Inventory inventory, PagesManager pagesManager) { + int lastRow = inventorySlots / 9; - // exit - setItem(inventory, Material.REDSTONE, this.nameExit, 4, height); + // Exit button in the center + addItem(inventory, Material.REDSTONE, exitLabel, 4, lastRow); - int count = pagesManager.getCountPages(); + int currentPage = pagesManager.getCurrentPage(); + int totalPages = pagesManager.getCountPages(); - if (count == 0) { return; } - - if (pagesManager.getCurrentPage() < count) - { - // arrow right - setArrow(inventory, this.nameArrowRight, 8, height); + if (currentPage < totalPages) { + addItem(inventory, Material.ARROW, arrowRightLabel, 8, lastRow); } - if (pagesManager.getCurrentPage() > 0) - { - // arrow left - setArrow(inventory, this.nameArrowLeft, 0, height); + if (currentPage > 0) { + addItem(inventory, Material.ARROW, arrowLeftLabel, 0, lastRow); } } - private synchronized void setArrow(Inventory inventory, String name, int x, int y) - { - setItem(inventory, Material.ARROW, name, x, y); - } + /** + * Places an item at a specific coordinate within the inventory. + */ + private void addItem(Inventory inventory, Material material, String name, int x, int y) { + ItemStack item = new ItemStack(material); + ItemMeta meta = item.getItemMeta(); - private synchronized void setItem(Inventory inventory, Material material, String name, int x, int y) - { - ItemStack item = new ItemStack(material, 1); - ItemMeta meta_item = item.getItemMeta(); + if (meta != null) { + meta.setDisplayName(name); + item.setItemMeta(meta); + } - meta_item.setDisplayName(name); - item.setItemMeta(meta_item); - - inventory.setItem( - InventoryItemsUtil.getCordinates(this.inventory_slots, x, y), - item - ); + int slot = InventoryItemsUtil.getSlotFromCoordinates(inventorySlots, x, y); + inventory.setItem(slot, item); } } diff --git a/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItems.java b/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItems.java index e5357f7..f0b0715 100644 --- a/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItems.java +++ b/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItems.java @@ -2,20 +2,37 @@ package tokarotik.giftapi.inventory.inventoryitems; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; - import tokarotik.giftapi.NBT.pages.PagesManager; import java.util.List; -public class InventoryItems -{ - private final int items_slots; +/** + * Handles rendering of item pages into a Bukkit Inventory. + */ +public class InventoryItems { - public InventoryItems(int items_slots) { this.items_slots = items_slots; } + private final int itemSlotLimit; - public synchronized void renderPage(PagesManager pagesManager, Inventory inventory) - { - List array = InventoryItemsUtil.getArrayToRender(pagesManager, items_slots); - InventoryItemsUtil.renderList(array, inventory, this.items_slots); + /** + * @param itemSlotLimit Number of item slots available for rendering (typically inventory size minus control slots). + */ + public InventoryItems(int itemSlotLimit) { + if (itemSlotLimit <= 0) { + throw new IllegalArgumentException("Item slot limit must be greater than 0."); + } + this.itemSlotLimit = itemSlotLimit; + } + + /** + * Renders a page of items from the PagesManager into the provided inventory. + * + * @param pagesManager the source of paginated items + * @param inventory the Bukkit inventory to be populated + */ + public void renderPage(PagesManager pagesManager, Inventory inventory) { + if (pagesManager == null || inventory == null) return; + + List itemsToRender = InventoryItemsUtil.getArrayToRender(pagesManager, itemSlotLimit); + InventoryItemsUtil.renderList(itemsToRender, inventory, itemSlotLimit); } } diff --git a/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItemsUtil.java b/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItemsUtil.java index 9db99b6..de6037a 100644 --- a/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItemsUtil.java +++ b/src/main/java/tokarotik/giftapi/inventory/inventoryitems/InventoryItemsUtil.java @@ -2,67 +2,108 @@ package tokarotik.giftapi.inventory.inventoryitems; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; - import tokarotik.giftapi.NBT.pages.PagesManager; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; -public class InventoryItemsUtil -{ - public synchronized static List getArrayToRender(PagesManager pagesManager, int items_slots) - { - ItemStack[] items = pagesManager.getList(); +/** + * Utility class for rendering item pages into inventories. + */ +public final class InventoryItemsUtil { - int items_count = items.length; - int current_page = pagesManager.getCurrentPage(); + private static final int INVENTORY_COLUMNS = 9; + private static final int MAX_ROW_WIDTH = 10; - int start_item = getStartItemIndex(current_page, items_slots); - int end_item = getEndItemIndex(current_page, items_slots, items_count); - - return Arrays.asList(items).subList(start_item, end_item); + private InventoryItemsUtil() { + throw new UnsupportedOperationException("Utility class should not be instantiated."); } - public synchronized static void renderList(List array, Inventory inventory, int items_slots) - { - short x = 0; - short y = 0; + /** + * Returns a sublist of the items for the current page from the PagesManager. + * + * @param pagesManager the source of item pages + * @param slotsPerPage how many item slots are available per page + * @return list of items to render on the current page + */ + public static List getArrayToRender(PagesManager pagesManager, int slotsPerPage) { + if (pagesManager == null || slotsPerPage <= 0) return new ArrayList<>(); - for (ItemStack itemStack : array) { + ItemStack[] allItems = pagesManager.getList(); + int totalItems = allItems.length; + int currentPage = pagesManager.getCurrentPage(); - if (x >= 10) { + int startIndex = getStartItemIndex(currentPage, slotsPerPage); + int endIndex = getEndItemIndex(currentPage, slotsPerPage, totalItems); + + return Arrays.asList(allItems).subList(startIndex, endIndex); + } + + /** + * Renders the list of items into the inventory based on a coordinate system. + * + * @param itemsToRender list of items to place into the inventory + * @param inventory the target Bukkit inventory + * @param slotsPerPage number of item slots available for rendering + */ + public static void renderList(List itemsToRender, Inventory inventory, int slotsPerPage) { + if (itemsToRender == null || inventory == null) return; + + int x = 0, y = 0; + int maxSlot = slotsPerPage - 1; + + for (ItemStack item : itemsToRender) { + if (x >= MAX_ROW_WIDTH) { x = 0; y++; } - int slot = getCordinates(items_slots + 9, x, y); - if (slot > items_slots - 1) { - return; + int slot = getSlotFromCoordinates(slotsPerPage + 9, x, y); // 9 added for controls + + if (slot > maxSlot) { + return; // Exceeded usable inventory area } - inventory.setItem(slot, itemStack); - + inventory.setItem(slot, item); x++; } } - private synchronized static int getStartItemIndex(int current_page, int items_slots) - { - return current_page * items_slots; + /** + * Calculates the start index for a page. + */ + private static int getStartItemIndex(int currentPage, int slotsPerPage) { + return currentPage * slotsPerPage; } - private synchronized static int getEndItemIndex(int current_page, int items_slots, int items_count) - { - items_slots += 4; - int literly_end_page = (current_page + 1) * items_slots; + /** + * Calculates the end index for a page, ensuring it doesn't exceed item count. + */ + private static int getEndItemIndex(int currentPage, int slotsPerPage, int totalItems) { + int paddedPageSize = slotsPerPage + 4; // May account for GUI spacing or reserved rows + int endIndex = (currentPage + 1) * paddedPageSize; - if (literly_end_page <= items_count) { return literly_end_page; } - return literly_end_page - (literly_end_page - items_count); + return Math.min(endIndex, totalItems); } - public static synchronized int getCordinates(int inventory_slots, int x, int y) - { - if (y > 0 && y != inventory_slots / 9) { x += y; } - return ((y * 9) + x) % (inventory_slots + 9); + /** + * Maps 2D coordinates into a 1D inventory slot index. + * Accounts for skipped rows or padding. + * + * @param totalInventorySlots total size of the inventory + * @param x column index + * @param y row index + * @return calculated 1D slot index + */ + public static int getSlotFromCoordinates(int totalInventorySlots, int x, int y) { + // Adjust X if the row is beyond the first (some layouts may shift items right) + if (y > 0 && y != totalInventorySlots / INVENTORY_COLUMNS) { + x += y; + } + + int slot = (y * INVENTORY_COLUMNS) + x; + return slot % totalInventorySlots; } } + diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index fd30855..a50a232 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,10 +1,6 @@ # For colors: # do not use § use only & - -# Will turn command /gift for dev. test -dev-test: true - gui: # Set Title Only In English # Max title length is 32 letters diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index daba45a..8252817 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,11 +1,3 @@ name: GiftAPI version: 1.0 -main: tokarotik.giftapi.Main - -commands: - gift: - description: for test - usage: / - add: - description: for test - usage: / \ No newline at end of file +main: tokarotik.giftapi.Main \ No newline at end of file