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.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, "<<right"),
ConfigPaths.getWithColor(config, ConfigPaths.GUILEFT, "left>>"),
ConfigPaths.getWithColor(config, ConfigPaths.GUIEXIT, "quit")
inventorySlots,
ConfigPaths.getWithColor(config, ConfigPaths.GUI_TITLE, "GiftAPI Menu"),
ConfigPaths.getWithColor(config, ConfigPaths.GUI_BUTTON_RIGHT, "<<right"),
ConfigPaths.getWithColor(config, ConfigPaths.GUI_BUTTON_LEFT, "left>>"),
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);
}
}

View File

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

View File

@ -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; }
}
public CacheManager getCacheManager() {
return cacheManager;
}
}

View File

@ -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<String> lore = nbtListToStringList(loreList);
meta.setLore(lore);
}
}
return meta;
}
private static List<String> 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<String>.
*
* @param nbt the NBTTagList containing NBTTagStrings
* @return a List of Strings extracted from the NBT list
*/
private static List<String> 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);
}
}

View File

@ -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<String> 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<Enchantment, Integer> enchantments)
{
NBTTagCompound NBTlist = new NBTTagCompound();
private static NBTTagList stringListToNBTList(List<String> 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<Enchantment, Integer> 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;
}
}

View File

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

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.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<ItemStack>
List<ItemStack> oldList = getListFromNBT(tag);
List<ItemStack> 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<ItemStack> 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<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 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"));
}
}

View File

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

View File

@ -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<Player, PagesManager> 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<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();
}
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<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.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<Player, PagesManager> world_map = new HashMap<>();
public final Map<Player, PagesManager> nether_map = new HashMap<>();
public final Map<Player, PagesManager> 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<Player, PagesManager> overworldCache = new HashMap<>();
private final Map<Player, PagesManager> netherCache = new HashMap<>();
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)
{
for (World world : Bukkit.getWorlds())
{
String nameWorld = world.getName();
Map<Player, PagesManager> 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<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();
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<Player, PagesManager> 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<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) {
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<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.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);
}
}

View File

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

View File

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

View File

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

View File

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