347 lines
11 KiB
Java
347 lines
11 KiB
Java
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<Map<String, Object>> 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<Integer> listKitSlots(KitType type) {
|
|
List<Integer> 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<List<ItemStack>> getKit(KitType type, int slot) {
|
|
validateSlot(slot);
|
|
|
|
List<?> raw = plugin.getConfig().getList(path(type, slot));
|
|
if (raw == null || raw.isEmpty()) {
|
|
return Optional.empty();
|
|
}
|
|
|
|
List<ItemStack> 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<List<ItemStack>> getKitForPlayer(UUID playerId, KitType type, int slot) {
|
|
Optional<List<ItemStack>> maybeBase = getKit(type, slot);
|
|
if (maybeBase.isEmpty()) {
|
|
return Optional.empty();
|
|
}
|
|
|
|
List<ItemStack> base = maybeBase.get();
|
|
Optional<int[]> 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<List<ItemStack>> maybeBase = getKit(type, slot);
|
|
if (maybeBase.isEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
List<ItemStack> base = maybeBase.get();
|
|
ItemStack[] arranged = normalizeContents(arrangedContents);
|
|
int[] layout = deriveLayout(base, arranged);
|
|
|
|
List<Integer> 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<int[]> 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<ItemStack> applyLayout(List<ItemStack> base, int[] layout) {
|
|
List<ItemStack> 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<ItemStack> 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<String, Object> 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<Integer> slots = new ArrayList<>();
|
|
List<Integer> 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<ItemStack> cloneInventory(List<ItemStack> contents) {
|
|
List<ItemStack> 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");
|
|
}
|
|
}
|
|
}
|