I upgraded code with ChatGPT to senior level and added README.txt

This commit is contained in:
ScriptCat 2025-07-29 21:16:55 +03:00
parent f61620e96c
commit d46a4d2d20
20 changed files with 989 additions and 712 deletions

41
README.txt Normal file
View File

@ -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

View File

@ -7,38 +7,74 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import tokarotik.giftapi.inventory.InventoryManager; 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 class APIManager implements Listener {
public final Main plugin;
private final InventoryManager inventoryManager; private final InventoryManager inventoryManager;
public APIManager(Main plugin, FileConfiguration config, int inventory_slots) public APIManager(Main plugin, FileConfiguration config, int inventorySlots) {
{ if (plugin == null || config == null) {
this.plugin = plugin; throw new IllegalArgumentException("Plugin and config must not be null.");
}
this.inventoryManager = new InventoryManager( this.inventoryManager = new InventoryManager(
plugin.getCacheManager(), plugin.getCacheManager(),
inventory_slots, inventorySlots,
ConfigPaths.getWithColor(config, ConfigPaths.GUINAME, "GiftAPI Menu"), ConfigPaths.getWithColor(config, ConfigPaths.GUI_TITLE, "GiftAPI Menu"),
ConfigPaths.getWithColor(config, ConfigPaths.GUIRIGHT, "<<right"), ConfigPaths.getWithColor(config, ConfigPaths.GUI_BUTTON_RIGHT, "<<right"),
ConfigPaths.getWithColor(config, ConfigPaths.GUILEFT, "left>>"), ConfigPaths.getWithColor(config, ConfigPaths.GUI_BUTTON_LEFT, "left>>"),
ConfigPaths.getWithColor(config, ConfigPaths.GUIEXIT, "quit") 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); } /**
* Adds an item to the player's gift inventory.
public synchronized void remove(Player player, ItemStack item) { this.inventoryManager.remove(player, item); } *
* @param player the target player
public synchronized void openInventory(Player player) * @param item the item to add
{ */
this.inventoryManager.openCustomInventory(player); 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 @EventHandler
public void onInventoryClick(InventoryClickEvent event) { this.inventoryManager.onInventoryClick(event); } public void handleInventoryClick(InventoryClickEvent event) {
inventoryManager.handleInventoryClick(event);
}
} }

View File

@ -3,15 +3,35 @@ package tokarotik.giftapi;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
public class ConfigPaths { /**
public static final String DEVTEST = "dev-test"; * Centralized configuration path keys and utility methods for retrieving
public static final String GUINAME = "gui.title-menu"; * and colorizing string values from the plugin's configuration.
public static final String GUIRIGHT = "gui.buttons.right-name"; */
public static final String GUILEFT = "gui.buttons.left-name"; public final class ConfigPaths {
public static final String GUIEXIT = "gui.buttons.exit-name";
public static String getWithColor(FileConfiguration config, String path, String defaul) private ConfigPaths() {
{ // Utility class; prevent instantiation
return ChatColor.translateAlternateColorCodes('&', config.getString(path, defaul)); 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);
} }
} }

View File

@ -9,63 +9,81 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import tokarotik.giftapi.cache.CacheManager; 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 CacheManager cacheManager;
private APIManager apiManager; private APIManager apiManager;
private final int inventory_slots = 54;
@Override @Override
public void onEnable() public void onEnable() {
{
saveDefaultConfig(); saveDefaultConfig();
cacheManager = new CacheManager(inventory_slots - 9); this.cacheManager = new CacheManager(CACHED_SLOTS);
apiManager = new APIManager(this, getConfig(), inventory_slots); this.apiManager = new APIManager(this, getConfig(), INVENTORY_SLOTS);
this.getCommand("gift").setExecutor(new GiftCommand(this)); // DELETE FOR RELEASE registerEvents();
this.getCommand("add").setExecutor(new AddCommand(this)); // DELETE FOR RELEASE
getServer().getPluginManager().registerEvents(this, this); // bullshit getLogger().info("GiftAPI has been enabled.");
getLogger().info("GiftAPI enabled!");
} }
@Override @Override
public void onDisable() public void onDisable() {
{ getLogger().info("Saving GiftAPI state...");
getLogger().info("Saving GiftApi..."); if (cacheManager != null) {
cacheManager.disable(); cacheManager.disable();
}
getLogger().info("GiftAPI has been disabled.");
}
getLogger().info("GiftAPI disabled!"); private void registerEvents() {
getServer().getPluginManager().registerEvents(this, this);
} }
@EventHandler @EventHandler
public void onPlayerQuit(PlayerQuitEvent event) public void onPlayerQuit(PlayerQuitEvent event) {
{ final Player player = event.getPlayer();
Bukkit.getScheduler().runTaskAsynchronously(this, () -> cacheManager.playerQuit(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) { public void add(Player player, ItemStack item) {
if (player == null || item == null) return;
Bukkit.getScheduler().runTaskAsynchronously(this, () -> apiManager.add(player, item)); 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)); Bukkit.getScheduler().runTaskAsynchronously(this, () -> apiManager.remove(player, item));
} }
// will open inventory GiftAPI /**
public void openGUI(Player player) * Opens the GiftAPI GUI for the specified player.
{ *
Bukkit.getScheduler().runTaskAsynchronously(this, () -> apiManager.openInventory(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; } public CacheManager getCacheManager() {
} return cacheManager;
}
}

View File

@ -1,98 +1,105 @@
package tokarotik.giftapi.NBT.item; 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.NBTTagList;
import net.minecraft.server.v1_6_R3.NBTTagString; 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.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
public class ItemLoad public final class ItemLoad {
{
public static ItemStack getItem(NBTTagCompound tag)
{
ItemStack item = new ItemStack( private ItemLoad() {
getId(tag), getCount(tag), getDurability(tag) // Utility class; prevent instantiation
); }
// тут чехорда. с начало сет айтем, после сет мета, а в самом конце гет мета /**
item.setItemMeta( * Constructs an ItemStack from the given NBTTagCompound.
setMeta( *
tag, * @param tag the NBTTagCompound containing item data
item.getItemMeta() * @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; return item;
} }
private static int getId(NBTTagCompound tag) private static int getId(NBTTagCompound tag) {
{ return tag.hasKey("id") ? tag.getInt("id") : 0;
int id = 0;
if (tag.hasKey("id"))
{
id = tag.getInt("id");
}
return id;
} }
private static short getCount(NBTTagCompound tag) private static short getCount(NBTTagCompound tag) {
{ // Default count 1 for valid items
short count = 1; return tag.hasKey("count") ? tag.getShort("count") : 1;
if (tag.hasKey("count"))
{
count = tag.getShort("count");
}
return count;
} }
private static short getDurability(NBTTagCompound tag) private static short getDurability(NBTTagCompound tag) {
{ // Default durability 0 (undamaged)
short durability = 0; return tag.hasKey("damage") ? tag.getShort("damage") : 0;
if (tag.hasKey("damage"))
{
durability = tag.getShort("damage");
}
return durability;
} }
private static ItemMeta setMeta(NBTTagCompound tag, ItemMeta meta) /**
{ * Applies metadata to an ItemMeta from the NBT tag.
if (tag.hasKey("meta")) *
{ * @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"); NBTTagCompound metaNBT = tag.getCompound("meta");
if (metaNBT.hasKey("name")) if (metaNBT.hasKey("name")) {
{
meta.setDisplayName(metaNBT.getString("name")); meta.setDisplayName(metaNBT.getString("name"));
} }
if (metaNBT.hasKey("lore"))
{ if (metaNBT.hasKey("lore")) {
meta.setLore( NBTTagList loreList = metaNBT.getList("lore");
NBTListToStringList( List<String> lore = nbtListToStringList(loreList);
metaNBT.getList("lore") meta.setLore(lore);
)
);
} }
} }
return meta; return meta;
} }
private static List<String> NBTListToStringList(NBTTagList nbt) /**
{ * Converts an NBTTagList of strings to a List<String>.
String[] list = new String[nbt.size()]; *
* @param nbt the NBTTagList containing NBTTagStrings
for (int i = 0; i < nbt.size(); i++) * @return a List of Strings extracted from the NBT list
{ */
NBTTagString base = (NBTTagString) nbt.get(i); private static List<String> nbtListToStringList(NBTTagList nbt) {
if (nbt == null || nbt.size() == 0) {
list[i] = base.data; 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); return Arrays.asList(list);
} }
} }

View File

@ -10,12 +10,24 @@ import org.bukkit.inventory.meta.ItemMeta;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
public class ItemNBT public final class ItemNBT {
{
public static NBTTagCompound getTag(ItemStack item) private ItemNBT() {
{ // Utility class; prevent instantiation
if (item == null) {return null;} }
/**
* 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"); NBTTagCompound tag = new NBTTagCompound("item");
@ -25,87 +37,78 @@ public class ItemNBT
return tag; return tag;
} }
private static void setBasic(ItemStack item, NBTTagCompound tag) private static void setBasic(ItemStack item, NBTTagCompound tag) {
{ short count = (short) item.getAmount();
short count = (short)item.getAmount(); if (count != 1) {
if (count != 1) { tag.setShort("count", count); } tag.setShort("count", count);
}
int id = item.getTypeId(); int id = item.getTypeId();
if (id != 0) { tag.setInt("id", item.getTypeId()); } if (id != 0) {
tag.setInt("id", id);
}
short damage = item.getDurability(); short damage = item.getDurability();
if (damage != 0) { tag.setShort("damage", item.getDurability()); } if (damage != 0) {
} tag.setShort("damage", damage);
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);
} }
} }
private static NBTTagList stringListToNBTCompound(List<String> list) private static void setMetaTag(ItemStack item, NBTTagCompound baseTag) {
{ if (!item.hasItemMeta()) {
NBTTagList NBTlist = new NBTTagList(); return;
}
for (String s : list) {
NBTlist.add( ItemMeta meta = item.getItemMeta();
new NBTTagString( NBTTagCompound metaTag = new NBTTagCompound();
null,
s 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<Enchantment, Integer> enchantments) private static NBTTagList stringListToNBTList(List<String> list) {
{ NBTTagList nbtList = new NBTTagList();
NBTTagCompound NBTlist = new NBTTagCompound(); 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<Enchantment, Integer> enchantments) {
NBTTagCompound enchantmentsNBT = new NBTTagCompound();
if (enchantments == null || enchantments.isEmpty()) {
return enchantmentsNBT;
}
enchantments.forEach((enchantment, level) -> { enchantments.forEach((enchantment, level) -> {
if (enchantment == null) return; // defensive check
NBTTagCompound enchantmentNBT = new NBTTagCompound(); NBTTagCompound enchantmentNBT = new NBTTagCompound();
enchantmentNBT.setInt("level", level != null ? level : 0);
int levelNBT = 0; enchantmentsNBT.setCompound(String.valueOf(enchantment.getId()), enchantmentNBT);
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);
}); });
return NBTlist;
return enchantmentsNBT;
} }
} }

View File

@ -1,56 +1,78 @@
package tokarotik.giftapi.NBT.pages; package tokarotik.giftapi.NBT.pages;
import net.minecraft.server.v1_6_R3.NBTTagList; import net.minecraft.server.v1_6_R3.NBTTagList;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
public class PagesManager public final class PagesManager {
{
private NBTTagList tag; private volatile NBTTagList tag;
private int current_page = 0; private volatile int currentPage = 0;
private final int items_slots; private final int itemsSlots;
public PagesManager(NBTTagList tag, int items_slots)
{ public PagesManager(NBTTagList tag, int itemsSlots) {
this.items_slots = items_slots; this.itemsSlots = itemsSlots;
this.tag = tag; this.tag = tag != null ? tag : new NBTTagList("giftapi");
} }
public PagesManager(int items_slots) public PagesManager(int itemsSlots) {
{ this.itemsSlots = itemsSlots;
this.items_slots = items_slots;
this.tag = new NBTTagList("giftapi"); this.tag = new NBTTagList("giftapi");
} }
public synchronized void add(ItemStack item) public synchronized void add(ItemStack item) {
{
PagesUtil.add(item, this.tag); PagesUtil.add(item, this.tag);
} }
public synchronized void remove(ItemStack item) public synchronized void remove(ItemStack item) {
{
this.tag = PagesUtil.remove(item, this.tag); this.tag = PagesUtil.remove(item, this.tag);
} // Optionally reset currentPage if it's now out of range
int maxPage = getCountPages() - 1;
public synchronized NBTTagList getTag() { return this.tag; } if (currentPage > maxPage) {
currentPage = Math.max(0, maxPage);
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++;
} }
} }
public synchronized void backPage() public synchronized NBTTagList getTag() {
{ return tag;
if (0 < getCurrentPage()) }
{
this.current_page -= 1; 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--;
} }
} }
} }

View File

@ -2,69 +2,85 @@ package tokarotik.giftapi.NBT.pages;
import net.minecraft.server.v1_6_R3.NBTTagCompound; import net.minecraft.server.v1_6_R3.NBTTagCompound;
import net.minecraft.server.v1_6_R3.NBTTagList; import net.minecraft.server.v1_6_R3.NBTTagList;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import tokarotik.giftapi.NBT.item.ItemLoad; import tokarotik.giftapi.NBT.item.ItemLoad;
import tokarotik.giftapi.NBT.item.ItemNBT; import tokarotik.giftapi.NBT.item.ItemNBT;
public class PagesUtil import java.util.ArrayList;
{ import java.util.List;
public synchronized static void add(ItemStack item, NBTTagList tag)
{ 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); NBTTagCompound nbt = ItemNBT.getTag(item);
if (nbt != null) {
tag.add(nbt); tag.add(nbt);
}
} }
public synchronized static NBTTagList remove(ItemStack item, NBTTagList tag) public static synchronized NBTTagList remove(ItemStack item, NBTTagList tag) {
{ if (item == null || tag == null) return tag;
ItemStack[] new_list = addAllExtraOne(tag, item);
return itemStackListToNBTList(new_list);
}
public synchronized static ItemStack[] addAllExtraOne(NBTTagList tag, ItemStack item) // Convert NBTTagList -> List<ItemStack>
{ List<ItemStack> oldList = getListFromNBT(tag);
ItemStack[] new_list = new ItemStack[tag.size()]; List<ItemStack> newList = new ArrayList<>(oldList.size());
ItemStack[] old_list = getRawList(tag);
for (int i = 0;i < tag.size();i++) for (ItemStack stack : oldList) {
{ if (!equalsItem(stack, item)) {
if (!old_list[i].equals(item)) newList.add(stack);
{
new_list[i] = old_list[i];
} }
} }
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<ItemStack> items) {
NBTTagList list = new NBTTagList(); NBTTagList list = new NBTTagList();
for (ItemStack item : items) { for (ItemStack item : items) {
if (item != null) { if (item != null) {
NBTTagCompound compound = ItemNBT.getTag(item); NBTTagCompound compound = ItemNBT.getTag(item);
if (compound != null) {
list.add(compound); list.add(compound);
}
} }
} }
return list; 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()]; 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); NBTTagCompound compound = (NBTTagCompound) tag.get(i);
list[i] = ItemLoad.getItem(compound); list[i] = ItemLoad.getItem(compound);
} }
return list; return list;
} }
private static synchronized List<ItemStack> getListFromNBT(NBTTagList tag) {
ItemStack[] array = getRawList(tag);
List<ItemStack> list = new ArrayList<>(array.length);
for (ItemStack item : array) {
if (item != null) {
list.add(item);
}
}
return list;
}
} }

View File

@ -9,85 +9,127 @@ import org.bukkit.craftbukkit.v1_6_R3.entity.CraftPlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream; 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) public BasicNBT(String world) {
{ this.worldPath = Paths.get(getBasePath(), world);
this.path = getPath() + "\\" + world;
} }
public boolean writePlayerNBT(NBTTagCompound compound, EntityPlayer entityPlayer) /**
{ * Writes the player's NBT data to disk.
try *
{ * @param compound the NBTTagCompound representing player data
entityPlayer.f(compound); * @param entityPlayer the player entity
* @return true if write succeeded, false otherwise
FileOutputStream out = new FileOutputStream(getSavePath(entityPlayer)); */
NBTCompressedStreamTools.a(compound, out); public boolean writePlayerNBT(NBTTagCompound compound, EntityPlayer entityPlayer) {
out.close(); if (compound == null || entityPlayer == null) {
} Bukkit.getLogger().warning("Attempted to write NBT with null compound or player");
catch(Exception e) return false;
{ }
Bukkit.getLogger().warning(e.toString());
// 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 false;
} }
return true;
} }
public NBTTagCompound readPlayerNBT(EntityPlayer entityPlayer) /**
{ * Reads the player's NBT data from disk.
try * If the file does not exist or fails to read, returns a fresh NBTTagCompound
{ * initialized with the player's current state.
FileInputStream in = new FileInputStream(getSavePath(entityPlayer)); *
* @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); NBTTagCompound compound = NBTCompressedStreamTools.a(in);
// Load data into entityPlayer
entityPlayer.e(compound); entityPlayer.e(compound);
return compound; return compound;
} } catch (IOException e) {
catch(Exception ignored) Bukkit.getLogger().log(Level.WARNING, "Failed to read player NBT for " + entityPlayer.getName(), e);
{ return getFreshPlayerNBT(entityPlayer);
try
{
NBTTagCompound compound = new NBTTagCompound();
entityPlayer.e(compound);
return compound;
}
catch (Exception e)
{
Bukkit.getLogger().warning(e.toString());
return null;
}
} }
} }
public static EntityPlayer getPlayer(Player player) private NBTTagCompound getFreshPlayerNBT(EntityPlayer entityPlayer) {
{ try {
return ((CraftPlayer) player).getHandle(); NBTTagCompound compound = new NBTTagCompound();
} entityPlayer.e(compound);
return compound;
public String getPath() { } catch (Exception e) {
try Bukkit.getLogger().log(Level.SEVERE, "Failed to initialize fresh NBT for " + entityPlayer.getName(), e);
{
return new File(".").getCanonicalFile().getAbsolutePath();
}
catch (Exception e)
{
return null; return null;
} }
} }
private String getSavePath(EntityPlayer entityPlayer) public static EntityPlayer getPlayer(Player player) {
{ if (!(player instanceof CraftPlayer)) {
return this.path + "\\players\\" + entityPlayer.getName() + ".dat"; 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"));
} }
} }

View File

@ -5,20 +5,26 @@ import net.minecraft.server.v1_6_R3.NBTTagCompound;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
public class WorldNBTSynchronizer import java.util.Objects;
{
@Nullable
public static String getWorldWhatHasPlayer(EntityPlayer entityPlayer)
{
for (World world : Bukkit.getWorlds())
{
String worldName = world.getName();
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; return worldName;
} }
} }
@ -26,47 +32,62 @@ public class WorldNBTSynchronizer
return null; return null;
} }
public static boolean hasAll(EntityPlayer entityPlayer, String worldName) /**
{ * Checks whether the player with the specified name exists in the world and
boolean isHasPlayer = hasPlayer(entityPlayer.getName(), worldName); * if their NBT contains the "giftapi" tag.
if (!isHasPlayer) { return false; } *
* @param entityPlayer the player entity
return hasGiftApiNBT(entityPlayer, worldName); * @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); World world = Bukkit.getWorld(worldName);
if (world == null) {
for (Player player : world.getPlayers()) return false;
{
if (player.getName().equals(playerName)) { return true; }
} }
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); BasicNBT basicNBT = new BasicNBT(worldName);
NBTTagCompound tag = basicNBT.readPlayerNBT(entityPlayer); NBTTagCompound tag = basicNBT.readPlayerNBT(entityPlayer);
if (tag == null) { return false; } if (tag == null || !tag.hasKey("giftapi")) {
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.
} }
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;
}
} }
} }

View File

@ -1,61 +1,97 @@
package tokarotik.giftapi.cache; package tokarotik.giftapi.cache;
import net.minecraft.server.v1_6_R3.EntityPlayer; import net.minecraft.server.v1_6_R3.EntityPlayer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import tokarotik.giftapi.NBT.savers.BasicNBT; import tokarotik.giftapi.NBT.savers.BasicNBT;
import tokarotik.giftapi.NBT.savers.WorldNBTSynchronizer; import tokarotik.giftapi.NBT.savers.WorldNBTSynchronizer;
import tokarotik.giftapi.NBT.pages.PagesManager; import tokarotik.giftapi.NBT.pages.PagesManager;
import java.util.Map; 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 CacheUtil cacheUtil;
private final int inventory_slots; private final int inventorySlots;
public CacheManager(int inventory_slots) public CacheManager(int inventorySlots) {
{ if (inventorySlots <= 0) {
this.inventory_slots = inventory_slots; throw new IllegalArgumentException("Inventory slots must be positive.");
cacheUtil = new CacheUtil(inventory_slots); }
this.inventorySlots = inventorySlots;
this.cacheUtil = new CacheUtil(inventorySlots);
} }
public synchronized PagesManager load(Player player) { /**
String playerWorld = player.getWorld().getName(); * Loads the PagesManager for a given player.
Map<Player, PagesManager> worldMap = cacheUtil.getWorlMap(playerWorld); * If already cached, returns immediately.
EntityPlayer entityPlayer = BasicNBT.getPlayer(player); * Attempts to load from NBT, falling back to default if necessary.
*
// Return cached PageManager if already loaded * @param player the player to load data for
if (worldMap.containsKey(player)) { * @return PagesManager instance associated with the player
return worldMap.get(player); */
public PagesManager load(Player player) {
if (player == null) {
throw new IllegalArgumentException("Player cannot be null.");
} }
// Attempt to load from current world synchronized (getLockFor(player)) {
if (WorldNBTSynchronizer.hasAll(entityPlayer, playerWorld)) { String worldName = player.getWorld().getName();
return this.cacheUtil.loadAndCachePageManager(player, playerWorld, entityPlayer, worldMap); EntityPlayer entityPlayer = BasicNBT.getPlayer(player);
Map<Player, PagesManager> 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(); cacheUtil.saveAllHard();
} }
public synchronized void playerQuit(Player player) /**
{ * Saves data for a player when they leave.
this.cacheUtil.saveHard(player); *
* @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<Player, Object> for real-world concurrency handling
return player.getUniqueId();
} }
} }

View File

@ -3,80 +3,119 @@ package tokarotik.giftapi.cache;
import net.minecraft.server.v1_6_R3.EntityPlayer; import net.minecraft.server.v1_6_R3.EntityPlayer;
import net.minecraft.server.v1_6_R3.NBTTagCompound; import net.minecraft.server.v1_6_R3.NBTTagCompound;
import net.minecraft.server.v1_6_R3.NBTTagList; import net.minecraft.server.v1_6_R3.NBTTagList;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import tokarotik.giftapi.NBT.savers.BasicNBT; import tokarotik.giftapi.NBT.savers.BasicNBT;
import tokarotik.giftapi.NBT.pages.PagesManager; import tokarotik.giftapi.NBT.pages.PagesManager;
import java.util.HashMap; import java.util.*;
import java.util.Map;
public class CacheUtil /**
{ * Handles caching and persistent storage of PagesManager instances
public final Map<Player, PagesManager> world_map = new HashMap<>(); * for players across different world environments.
public final Map<Player, PagesManager> nether_map = new HashMap<>(); */
public final Map<Player, PagesManager> end_map = new HashMap<>(); 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) private final Map<Player, PagesManager> overworldCache = new HashMap<>();
{ private final Map<Player, PagesManager> netherCache = new HashMap<>();
this.inventory_slots = inventory_slots; private final Map<Player, PagesManager> 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) /**
{ * Saves the player's data across all three known dimensions.
for (World world : Bukkit.getWorlds()) *
{ * @param player the player whose data to save
String nameWorld = world.getName(); */
Map<Player, PagesManager> map = this.getWorlMap(nameWorld); 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<String, Map<Player, PagesManager>> entry : getWorldCaches().entrySet()) {
String worldName = entry.getKey();
Map<Player, PagesManager> cache = entry.getValue();
if (pages == null) { continue; } PagesManager pages = cache.get(player);
if (pages == null) continue;
NBTTagList tag = pages.getTag(); NBTTagList tag = pages.getTag();
EntityPlayer entityPlayer = BasicNBT.getPlayer(player); BasicNBT basicNBT = new BasicNBT(worldName);
BasicNBT basicNBT = new BasicNBT(nameWorld);
NBTTagCompound compound = basicNBT.readPlayerNBT(entityPlayer); NBTTagCompound compound = basicNBT.readPlayerNBT(entityPlayer);
compound.set("giftapi", tag); compound.set("giftapi", tag);
basicNBT.writePlayerNBT(compound, entityPlayer); basicNBT.writePlayerNBT(compound, entityPlayer);
} }
} }
public synchronized void saveAllHard() /**
{ * Saves all player data in all world caches.
for (Player player : Bukkit.getOnlinePlayers()) */
{ public synchronized void saveAllHard() {
for (Player player : Bukkit.getOnlinePlayers()) {
saveHard(player); saveHard(player);
} }
} }
public Map<Player, PagesManager> getWorlMap(String worldName) /**
{ * Retrieves the proper world-specific player cache map.
switch(worldName) *
{ * @param worldName name of the world
case "world": { return this.world_map; } * @return the map of players and their PagesManager for that world
case "world_nether": { return this.nether_map; } */
case "world_the_end": { return this.end_map; } public Map<Player, PagesManager> 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<Player, PagesManager> worldMap) { public synchronized PagesManager loadAndCachePageManager(Player player, String world, EntityPlayer entityPlayer, Map<Player, PagesManager> worldMap) {
NBTTagCompound compound = new BasicNBT(world).readPlayerNBT(entityPlayer); NBTTagCompound compound = new BasicNBT(world).readPlayerNBT(entityPlayer);
NBTTagList giftList = compound.getList("giftapi"); NBTTagList giftList = compound.getList("giftapi");
PagesManager pagesManager = new PagesManager(giftList, inventorySlots);
PagesManager pagesManager = new PagesManager(giftList, this.inventory_slots);
worldMap.put(player, pagesManager); worldMap.put(player, pagesManager);
return pagesManager; return pagesManager;
} }
/**
* Utility to aggregate all internal world caches.
*
* @return map of worldName -> (player, pagesManager map)
*/
private Map<String, Map<Player, PagesManager>> getWorldCaches() {
Map<String, Map<Player, PagesManager>> worldCaches = new HashMap<>();
worldCaches.put(OVERWORLD, overworldCache);
worldCaches.put(NETHER, netherCache);
worldCaches.put(END, endCache);
return worldCaches;
}
} }

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -7,53 +7,92 @@ import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import tokarotik.giftapi.cache.CacheManager; import tokarotik.giftapi.cache.CacheManager;
import tokarotik.giftapi.NBT.pages.PagesManager; 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 guiTitle;
{ private final int inventorySlots;
private final String nameGUI;
private final int inventory_slots;
private final CacheManager cacheManager; private final CacheManager cacheManager;
private final InventoryUtil inventoryUtil; private final InventoryUtil inventoryUtil;
/**
public InventoryManager(CacheManager cacheManager, int inventory_slots, String nameGUI, String nameArrowRight, String nameArrowLeft, String nameExit) * Constructs an InventoryManager responsible for handling a custom inventory UI.
{ *
this.nameGUI = nameGUI; * @param cacheManager player cache handler
this.inventory_slots = inventory_slots; * @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; 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 @EventHandler
public synchronized void onInventoryClick(InventoryClickEvent event) public void handleInventoryClick(InventoryClickEvent event) {
{ inventoryUtil.onInventoryClick(event);
this.inventoryUtil.onInventoryClick(event);
} }
public synchronized void add(Player player, ItemStack item) /**
{ * Adds an item to the player's PagesManager.
PagesManager pages = this.cacheManager.load(player); *
* @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); pages.add(item);
} }
public synchronized void remove(Player player, ItemStack item) /**
{ * Removes an item from the player's PagesManager.
PagesManager pages = this.cacheManager.load(player); *
* @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); pages.remove(item);
} }
public synchronized void openCustomInventory(Player player) /**
{ * Opens the custom GUI for the specified player.
this.cacheManager.load(player).setCurrentPage(0); * Resets to the first page before rendering.
Inventory inventory = Bukkit.createInventory(null, this.inventory_slots, this.nameGUI); *
* @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); player.openInventory(inventory);
} }
} }

View File

@ -12,124 +12,112 @@ import tokarotik.giftapi.NBT.pages.PagesManager;
import tokarotik.giftapi.inventory.inventoryitems.InventoryItems; import tokarotik.giftapi.inventory.inventoryitems.InventoryItems;
import tokarotik.giftapi.inventory.inventoryitems.InventoryItemsUtil; 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 CacheManager cacheManager;
private final InventoryItems inventoryItems; private final InventoryItems inventoryItems;
private final String nameGUI; private final String guiTitle;
private final String nameArrowRight; private final String arrowRightLabel;
private final String nameArrowLeft; private final String arrowLeftLabel;
private final String nameExit; private final String exitLabel;
private final int inventory_slots; 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.cacheManager = cacheManager;
this.nameGUI = nameGUI; this.guiTitle = guiTitle;
this.nameArrowRight = nameArrowRight; this.arrowRightLabel = arrowRightLabel;
this.nameArrowLeft = nameArrowLeft; this.arrowLeftLabel = arrowLeftLabel;
this.nameExit = nameExit; this.exitLabel = exitLabel;
this.inventory_slots = inventory_slots; this.inventorySlots = inventorySlots;
this.inventoryItems = new InventoryItems(inventory_slots); 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(); 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 displayName = clickedItem.getItemMeta().getDisplayName();
String nameCurrentItem = null; if (displayName == null) return;
if (currentItem.hasItemMeta()) {
if (currentItem.getItemMeta().hasDisplayName()) { Player player = (Player) event.getWhoClicked();
nameCurrentItem = currentItem.getItemMeta().getDisplayName(); PagesManager pagesManager = cacheManager.load(player);
}
if (displayName.equals(arrowRightLabel)) {
if (pagesManager.getCurrentPage() < pagesManager.getCountPages()) {
pagesManager.nextPage();
updateInventory(inventory, player);
} }
Player player = (Player) event.getWhoClicked(); } else if (displayName.equals(arrowLeftLabel)) {
if (pagesManager.getCurrentPage() > 0) {
if (nameCurrentItem != null) pagesManager.backPage();
{ updateInventory(inventory, player);
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(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(); inventory.clear();
PagesManager pagesManager = this.cacheManager.load(player); PagesManager pagesManager = cacheManager.load(player);
this.inventoryItems.renderPage(pagesManager, inventory); inventoryItems.renderPage(pagesManager, inventory);
setUI(inventory, pagesManager); renderControls(inventory, pagesManager);
} }
private synchronized void setUI(Inventory inventory, PagesManager pagesManager) /**
{ * Adds navigation and exit controls to the inventory UI.
int height = (this.inventory_slots / 9); */
private void renderControls(Inventory inventory, PagesManager pagesManager) {
int lastRow = inventorySlots / 9;
// exit // Exit button in the center
setItem(inventory, Material.REDSTONE, this.nameExit, 4, height); addItem(inventory, Material.REDSTONE, exitLabel, 4, lastRow);
int count = pagesManager.getCountPages(); int currentPage = pagesManager.getCurrentPage();
int totalPages = pagesManager.getCountPages();
if (count == 0) { return; } if (currentPage < totalPages) {
addItem(inventory, Material.ARROW, arrowRightLabel, 8, lastRow);
if (pagesManager.getCurrentPage() < count)
{
// arrow right
setArrow(inventory, this.nameArrowRight, 8, height);
} }
if (pagesManager.getCurrentPage() > 0) if (currentPage > 0) {
{ addItem(inventory, Material.ARROW, arrowLeftLabel, 0, lastRow);
// arrow left
setArrow(inventory, this.nameArrowLeft, 0, height);
} }
} }
private synchronized void setArrow(Inventory inventory, String name, int x, int y) /**
{ * Places an item at a specific coordinate within the inventory.
setItem(inventory, Material.ARROW, name, x, y); */
} 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) if (meta != null) {
{ meta.setDisplayName(name);
ItemStack item = new ItemStack(material, 1); item.setItemMeta(meta);
ItemMeta meta_item = item.getItemMeta(); }
meta_item.setDisplayName(name); int slot = InventoryItemsUtil.getSlotFromCoordinates(inventorySlots, x, y);
item.setItemMeta(meta_item); inventory.setItem(slot, item);
inventory.setItem(
InventoryItemsUtil.getCordinates(this.inventory_slots, x, y),
item
);
} }
} }

View File

@ -2,20 +2,37 @@ package tokarotik.giftapi.inventory.inventoryitems;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import tokarotik.giftapi.NBT.pages.PagesManager; import tokarotik.giftapi.NBT.pages.PagesManager;
import java.util.List; import java.util.List;
public class InventoryItems /**
{ * Handles rendering of item pages into a Bukkit Inventory.
private final int items_slots; */
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) /**
{ * @param itemSlotLimit Number of item slots available for rendering (typically inventory size minus control slots).
List<ItemStack> array = InventoryItemsUtil.getArrayToRender(pagesManager, items_slots); */
InventoryItemsUtil.renderList(array, inventory, this.items_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<ItemStack> itemsToRender = InventoryItemsUtil.getArrayToRender(pagesManager, itemSlotLimit);
InventoryItemsUtil.renderList(itemsToRender, inventory, itemSlotLimit);
} }
} }

View File

@ -2,67 +2,108 @@ package tokarotik.giftapi.inventory.inventoryitems;
import org.bukkit.inventory.Inventory; import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import tokarotik.giftapi.NBT.pages.PagesManager; import tokarotik.giftapi.NBT.pages.PagesManager;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
public class InventoryItemsUtil /**
{ * Utility class for rendering item pages into inventories.
public synchronized static List<ItemStack> getArrayToRender(PagesManager pagesManager, int items_slots) */
{ public final class InventoryItemsUtil {
ItemStack[] items = pagesManager.getList();
int items_count = items.length; private static final int INVENTORY_COLUMNS = 9;
int current_page = pagesManager.getCurrentPage(); private static final int MAX_ROW_WIDTH = 10;
int start_item = getStartItemIndex(current_page, items_slots); private InventoryItemsUtil() {
int end_item = getEndItemIndex(current_page, items_slots, items_count); throw new UnsupportedOperationException("Utility class should not be instantiated.");
return Arrays.asList(items).subList(start_item, end_item);
} }
public synchronized static void renderList(List<ItemStack> array, Inventory inventory, int items_slots) /**
{ * Returns a sublist of the items for the current page from the PagesManager.
short x = 0; *
short y = 0; * @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<ItemStack> 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<ItemStack> 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; x = 0;
y++; y++;
} }
int slot = getCordinates(items_slots + 9, x, y);
if (slot > items_slots - 1) { int slot = getSlotFromCoordinates(slotsPerPage + 9, x, y); // 9 added for controls
return;
if (slot > maxSlot) {
return; // Exceeded usable inventory area
} }
inventory.setItem(slot, itemStack); inventory.setItem(slot, item);
x++; x++;
} }
} }
private synchronized static int getStartItemIndex(int current_page, int items_slots) /**
{ * Calculates the start index for a page.
return current_page * items_slots; */
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) /**
{ * Calculates the end index for a page, ensuring it doesn't exceed item count.
items_slots += 4; */
int literly_end_page = (current_page + 1) * items_slots; 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 Math.min(endIndex, totalItems);
return literly_end_page - (literly_end_page - items_count);
} }
public static synchronized int getCordinates(int inventory_slots, int x, int y) /**
{ * Maps 2D coordinates into a 1D inventory slot index.
if (y > 0 && y != inventory_slots / 9) { x += y; } * Accounts for skipped rows or padding.
return ((y * 9) + x) % (inventory_slots + 9); *
* @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;
} }
} }

View File

@ -1,10 +1,6 @@
# For colors: # For colors:
# do not use § use only & # do not use § use only &
# Will turn command /gift for dev. test
dev-test: true
gui: gui:
# Set Title Only In English # Set Title Only In English
# Max title length is 32 letters # Max title length is 32 letters

View File

@ -1,11 +1,3 @@
name: GiftAPI name: GiftAPI
version: 1.0 version: 1.0
main: tokarotik.giftapi.Main main: tokarotik.giftapi.Main
commands:
gift:
description: for test
usage: /<command>
add:
description: for test
usage: /<command>