package de.winniepat.pierredufaulersack.kits; import java.util.*; import java.util.concurrent.ThreadLocalRandom; import java.util.function.IntPredicate; import java.util.UUID; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; public class KitService { private static final int MIN_SLOT = 1; private static final int MAX_SLOT = 3; private static final int KIT_CONTENT_SIZE = 41; private final JavaPlugin plugin; public KitService(JavaPlugin plugin) { this.plugin = plugin; } public boolean addKit(KitType type, int slot, Player player) { validateSlot(slot); String path = path(type, slot); if (plugin.getConfig().contains(path)) { return false; } setKit(type, slot, player); return true; } public void setKit(KitType type, int slot, Player player) { setKit(type, slot, player.getInventory().getContents()); } public void setKit(KitType type, int slot, ItemStack[] contents) { validateSlot(slot); ItemStack[] normalizedContents = normalizeContents(contents); List> serialized = new ArrayList<>(normalizedContents.length); for (ItemStack item : normalizedContents) { serialized.add(item == null ? null : item.clone().serialize()); } plugin.getConfig().set(path(type, slot), serialized); plugin.saveConfig(); } public boolean deleteKit(KitType type, int slot) { validateSlot(slot); String path = path(type, slot); if (!plugin.getConfig().contains(path)) { return false; } plugin.getConfig().set(path, null); plugin.saveConfig(); return true; } public List listKitSlots(KitType type) { List slots = new ArrayList<>(); for (int slot = MIN_SLOT; slot <= MAX_SLOT; slot++) { if (hasKit(type, slot)) { slots.add(slot); } } return slots; } public boolean hasKit(KitType type, int slot) { validateSlot(slot); return plugin.getConfig().contains(path(type, slot)); } public Optional> getKit(KitType type, int slot) { validateSlot(slot); List raw = plugin.getConfig().getList(path(type, slot)); if (raw == null || raw.isEmpty()) { return Optional.empty(); } List saved = new ArrayList<>(KIT_CONTENT_SIZE); boolean hasItem = false; for (int index = 0; index < KIT_CONTENT_SIZE; index++) { Object element = index < raw.size() ? raw.get(index) : null; ItemStack item = deserializeItem(element); if (item != null) { hasItem = true; } saved.add(item); } if (!hasItem) { return Optional.empty(); } return Optional.of(cloneInventory(saved)); } public Optional> getKitForPlayer(UUID playerId, KitType type, int slot) { Optional> maybeBase = getKit(type, slot); if (maybeBase.isEmpty()) { return Optional.empty(); } List base = maybeBase.get(); Optional maybeLayout = getPlayerLayout(playerId, type, slot); if (maybeLayout.isEmpty()) { return Optional.of(cloneInventory(base)); } return Optional.of(applyLayout(base, maybeLayout.get())); } public boolean setPlayerLayout(UUID playerId, KitType type, int slot, ItemStack[] arrangedContents) { validateSlot(slot); Optional> maybeBase = getKit(type, slot); if (maybeBase.isEmpty()) { return false; } List base = maybeBase.get(); ItemStack[] arranged = normalizeContents(arrangedContents); int[] layout = deriveLayout(base, arranged); List serialized = new ArrayList<>(layout.length); for (int value : layout) { serialized.add(value); } plugin.getConfig().set(playerLayoutPath(playerId, type, slot), serialized); plugin.saveConfig(); return true; } private Optional getPlayerLayout(UUID playerId, KitType type, int slot) { String path = playerLayoutPath(playerId, type, slot); List raw = plugin.getConfig().getList(path); if (raw == null || raw.size() != KIT_CONTENT_SIZE) { return Optional.empty(); } int[] layout = new int[KIT_CONTENT_SIZE]; for (int i = 0; i < KIT_CONTENT_SIZE; i++) { Object element = raw.get(i); if (!(element instanceof Number number)) { return Optional.empty(); } int value = number.intValue(); if (value < 0 || value >= KIT_CONTENT_SIZE) { return Optional.empty(); } layout[i] = value; } return Optional.of(layout); } private static List applyLayout(List base, int[] layout) { List arranged = new ArrayList<>(KIT_CONTENT_SIZE); boolean[] consumed = new boolean[KIT_CONTENT_SIZE]; for (int target = 0; target < KIT_CONTENT_SIZE; target++) { int source = target < layout.length ? layout[target] : target; if (source < 0 || source >= KIT_CONTENT_SIZE || consumed[source]) { source = firstUnused(consumed, target); } consumed[source] = true; ItemStack item = source < base.size() ? base.get(source) : null; arranged.add(item == null ? null : item.clone()); } return arranged; } private static int[] deriveLayout(List base, ItemStack[] arranged) { int[] layout = new int[KIT_CONTENT_SIZE]; boolean[] used = new boolean[KIT_CONTENT_SIZE]; for (int target = 0; target < KIT_CONTENT_SIZE; target++) { ItemStack desired = arranged[target]; int matched = -1; for (int source = 0; source < KIT_CONTENT_SIZE; source++) { if (used[source]) { continue; } ItemStack sourceItem = source < base.size() ? base.get(source) : null; if (isSameStack(sourceItem, desired)) { matched = source; break; } } if (matched == -1) { matched = firstUnused(used, target); } used[matched] = true; layout[target] = matched; } return layout; } private static int firstUnused(boolean[] used, int fallback) { if (fallback >= 0 && fallback < used.length && !used[fallback]) { return fallback; } for (int i = 0; i < used.length; i++) { if (!used[i]) { return i; } } return 0; } private static boolean isSameStack(ItemStack a, ItemStack b) { if (a == null || a.getType().isAir()) { return b == null || b.getType().isAir(); } if (b == null || b.getType().isAir()) { return false; } return a.getAmount() == b.getAmount() && a.isSimilar(b); } private static ItemStack deserializeItem(Object element) { if (element == null) { return null; } if (element instanceof ItemStack itemStack) { return itemStack.clone(); } if (element instanceof Map map) { Map serialized = new LinkedHashMap<>(); for (Map.Entry entry : map.entrySet()) { if (entry.getKey() instanceof String key) { serialized.put(key, entry.getValue()); } } if (!serialized.isEmpty()) { try { return ItemStack.deserialize(serialized); } catch (IllegalArgumentException ignored) { return null; } } } return null; } public OptionalInt rollKitSlot(KitType type, IntPredicate allowedSlot) { List slots = new ArrayList<>(); List weights = new ArrayList<>(); int total = 0; for (int slot = MIN_SLOT; slot <= MAX_SLOT; slot++) { if (!allowedSlot.test(slot) || !hasKit(type, slot)) { continue; } int weight = getChance(type, slot); if (weight <= 0) { continue; } slots.add(slot); weights.add(weight); total += weight; } if (total <= 0) { return OptionalInt.empty(); } int roll = ThreadLocalRandom.current().nextInt(total); int cursor = 0; for (int i = 0; i < slots.size(); i++) { cursor += weights.get(i); if (roll < cursor) { return OptionalInt.of(slots.get(i)); } } return OptionalInt.empty(); } public OptionalInt rollKitSlot(KitType type) { return rollKitSlot(type, slot -> true); } private int getChance(KitType type, int slot) { String chancePath = "kit-chances." + type.key() + "." + slot; return plugin.getConfig().getInt(chancePath, 0); } private static ItemStack[] normalizeContents(ItemStack[] contents) { ItemStack[] normalized = new ItemStack[KIT_CONTENT_SIZE]; if (contents == null) { return normalized; } int copyLength = Math.min(KIT_CONTENT_SIZE, contents.length); for (int index = 0; index < copyLength; index++) { ItemStack item = contents[index]; normalized[index] = item == null ? null : item.clone(); } return normalized; } private static List cloneInventory(List contents) { List cloned = new ArrayList<>(contents.size()); for (ItemStack item : contents) { cloned.add(item == null ? null : item.clone()); } return cloned; } private static String path(KitType type, int slot) { return "kits." + type.key() + "." + slot; } private static String playerLayoutPath(UUID playerId, KitType type, int slot) { return "player-kit-layouts." + playerId + "." + type.key() + "." + slot; } private static void validateSlot(int slot) { if (slot < MIN_SLOT || slot > MAX_SLOT) { throw new IllegalArgumentException("Slot must be between 1 and 3"); } } }