2022-07-27 11:05:52 +02:00
|
|
|
package net.citizensnpcs.trait.shop;
|
|
|
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.function.Consumer;
|
|
|
|
import java.util.stream.Collectors;
|
2023-04-05 20:35:08 +02:00
|
|
|
import java.util.stream.IntStream;
|
2022-07-27 11:05:52 +02:00
|
|
|
|
2022-10-26 09:44:03 +02:00
|
|
|
import org.bukkit.ChatColor;
|
2022-07-27 11:05:52 +02:00
|
|
|
import org.bukkit.Material;
|
|
|
|
import org.bukkit.entity.Entity;
|
|
|
|
import org.bukkit.entity.HumanEntity;
|
|
|
|
import org.bukkit.inventory.Inventory;
|
|
|
|
import org.bukkit.inventory.InventoryHolder;
|
|
|
|
import org.bukkit.inventory.ItemStack;
|
2022-10-26 09:44:03 +02:00
|
|
|
import org.bukkit.inventory.meta.Damageable;
|
2022-07-27 11:05:52 +02:00
|
|
|
|
2022-12-03 17:59:16 +01:00
|
|
|
import com.google.common.base.Joiner;
|
2022-07-27 11:05:52 +02:00
|
|
|
import com.google.common.collect.Lists;
|
|
|
|
|
2022-12-03 11:21:46 +01:00
|
|
|
import net.citizensnpcs.api.gui.InputMenus;
|
2022-07-27 11:05:52 +02:00
|
|
|
import net.citizensnpcs.api.gui.InventoryMenuPage;
|
|
|
|
import net.citizensnpcs.api.gui.InventoryMenuSlot;
|
|
|
|
import net.citizensnpcs.api.gui.Menu;
|
|
|
|
import net.citizensnpcs.api.gui.MenuContext;
|
2022-12-03 11:21:46 +01:00
|
|
|
import net.citizensnpcs.api.jnbt.CompoundTag;
|
|
|
|
import net.citizensnpcs.api.jnbt.Tag;
|
2022-07-27 11:05:52 +02:00
|
|
|
import net.citizensnpcs.api.persistence.Persist;
|
2023-05-28 19:08:48 +02:00
|
|
|
import net.citizensnpcs.api.util.SpigotUtil;
|
2022-12-03 11:21:46 +01:00
|
|
|
import net.citizensnpcs.util.NMS;
|
2022-07-27 11:05:52 +02:00
|
|
|
import net.citizensnpcs.util.Util;
|
|
|
|
|
|
|
|
public class ItemAction extends NPCShopAction {
|
2022-12-03 11:21:46 +01:00
|
|
|
@Persist
|
2022-12-03 14:32:58 +01:00
|
|
|
public boolean compareSimilarity = true;
|
2022-07-27 11:05:52 +02:00
|
|
|
@Persist
|
|
|
|
public List<ItemStack> items = Lists.newArrayList();
|
2022-10-26 09:44:03 +02:00
|
|
|
@Persist
|
2022-12-03 11:21:46 +01:00
|
|
|
public List<String> metaFilter = Lists.newArrayList();
|
|
|
|
@Persist
|
2022-10-26 09:44:03 +02:00
|
|
|
public boolean requireUndamaged = true;
|
2022-07-27 11:05:52 +02:00
|
|
|
|
|
|
|
public ItemAction() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public ItemAction(ItemStack... items) {
|
|
|
|
this(Arrays.asList(items));
|
|
|
|
}
|
|
|
|
|
|
|
|
public ItemAction(List<ItemStack> items) {
|
|
|
|
this.items = items;
|
|
|
|
}
|
|
|
|
|
2023-06-18 20:27:13 +02:00
|
|
|
private boolean containsItems(Inventory source, int repeats, boolean modify) {
|
2023-04-05 20:35:08 +02:00
|
|
|
List<Integer> req = items.stream().map(i -> i.getAmount() * repeats).collect(Collectors.toList());
|
2022-10-26 09:44:03 +02:00
|
|
|
ItemStack[] contents = source.getContents();
|
|
|
|
for (int i = 0; i < contents.length; i++) {
|
2023-04-05 20:35:08 +02:00
|
|
|
ItemStack toMatch = contents[i];
|
2023-11-05 13:58:37 +01:00
|
|
|
if (toMatch == null || toMatch.getType() == Material.AIR || tooDamaged(toMatch)) {
|
2022-10-26 09:44:03 +02:00
|
|
|
continue;
|
2023-11-05 13:58:37 +01:00
|
|
|
}
|
2023-06-18 20:27:13 +02:00
|
|
|
toMatch = toMatch.clone();
|
2022-12-03 11:21:46 +01:00
|
|
|
for (int j = 0; j < items.size(); j++) {
|
2023-11-05 13:58:37 +01:00
|
|
|
if (toMatch == null) {
|
2023-06-18 19:44:29 +02:00
|
|
|
break;
|
2023-11-05 13:58:37 +01:00
|
|
|
}
|
2023-04-05 20:35:08 +02:00
|
|
|
ItemStack item = items.get(j);
|
2023-11-05 13:58:37 +01:00
|
|
|
if (req.get(j) <= 0 || !matches(item, toMatch)) {
|
2022-12-03 11:21:46 +01:00
|
|
|
continue;
|
2023-11-05 13:58:37 +01:00
|
|
|
}
|
2022-12-03 11:21:46 +01:00
|
|
|
int remaining = req.get(j);
|
2023-04-05 20:35:08 +02:00
|
|
|
int taken = toMatch.getAmount() > remaining ? remaining : toMatch.getAmount();
|
2023-06-18 20:27:13 +02:00
|
|
|
|
|
|
|
if (toMatch.getAmount() == taken) {
|
|
|
|
toMatch = null;
|
2022-12-03 11:21:46 +01:00
|
|
|
} else {
|
2023-06-18 20:27:13 +02:00
|
|
|
toMatch.setAmount(toMatch.getAmount() - taken);
|
|
|
|
}
|
|
|
|
if (modify) {
|
|
|
|
if (toMatch == null) {
|
|
|
|
source.clear(i);
|
|
|
|
} else {
|
|
|
|
source.setItem(i, toMatch.clone());
|
|
|
|
}
|
2022-12-03 11:21:46 +01:00
|
|
|
}
|
|
|
|
req.set(j, remaining - taken);
|
2022-10-26 09:44:03 +02:00
|
|
|
}
|
|
|
|
}
|
2022-12-03 11:21:46 +01:00
|
|
|
return req.stream().collect(Collectors.summingInt(n -> n)) <= 0;
|
2022-10-26 09:44:03 +02:00
|
|
|
}
|
|
|
|
|
2022-11-28 16:10:50 +01:00
|
|
|
@Override
|
|
|
|
public String describe() {
|
2023-11-05 13:58:37 +01:00
|
|
|
if (items.size() == 1)
|
2023-01-01 16:28:21 +01:00
|
|
|
return items.get(0).getAmount() + " " + Util.prettyEnum(items.get(0).getType());
|
2022-11-28 16:10:50 +01:00
|
|
|
String description = items.size() + " items";
|
|
|
|
for (int i = 0; i < items.size(); i++) {
|
|
|
|
ItemStack item = items.get(i);
|
|
|
|
description += "\n" + item.getAmount() + " " + Util.prettyEnum(item.getType());
|
|
|
|
if (i == 3) {
|
|
|
|
description += "...";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return description;
|
|
|
|
}
|
|
|
|
|
2022-07-27 11:05:52 +02:00
|
|
|
@Override
|
2023-04-05 20:35:08 +02:00
|
|
|
public int getMaxRepeats(Entity entity) {
|
|
|
|
if (!(entity instanceof InventoryHolder))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
Inventory source = ((InventoryHolder) entity).getInventory();
|
2023-11-05 13:58:37 +01:00
|
|
|
List<Integer> req = items.stream().map(ItemStack::getAmount).collect(Collectors.toList());
|
2023-04-05 20:35:08 +02:00
|
|
|
List<Integer> has = items.stream().map(i -> 0).collect(Collectors.toList());
|
|
|
|
ItemStack[] contents = source.getContents();
|
|
|
|
for (int i = 0; i < contents.length; i++) {
|
|
|
|
ItemStack toMatch = contents[i];
|
2023-11-05 13:58:37 +01:00
|
|
|
if (toMatch == null || toMatch.getType() == Material.AIR || tooDamaged(toMatch))
|
2023-04-05 20:35:08 +02:00
|
|
|
continue;
|
2023-11-05 13:58:37 +01:00
|
|
|
|
2023-06-19 14:39:50 +02:00
|
|
|
toMatch = toMatch.clone();
|
2023-04-05 20:35:08 +02:00
|
|
|
for (int j = 0; j < items.size(); j++) {
|
|
|
|
if (!matches(items.get(j), toMatch))
|
|
|
|
continue;
|
2023-11-05 13:58:37 +01:00
|
|
|
|
2023-06-19 14:39:50 +02:00
|
|
|
int remaining = req.get(j);
|
|
|
|
int taken = toMatch.getAmount() > remaining ? remaining : toMatch.getAmount();
|
|
|
|
has.set(j, has.get(j) + taken);
|
2023-11-05 13:58:37 +01:00
|
|
|
if (toMatch.getAmount() - taken <= 0) {
|
2023-06-19 14:39:50 +02:00
|
|
|
break;
|
2023-11-05 13:58:37 +01:00
|
|
|
}
|
2023-06-19 14:39:50 +02:00
|
|
|
toMatch.setAmount(toMatch.getAmount() - taken);
|
2023-04-05 20:35:08 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return IntStream.range(0, req.size()).map(i -> req.get(i) == 0 ? 0 : has.get(i) / req.get(i)).reduce(Math::min)
|
|
|
|
.orElse(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Transaction grant(Entity entity, int repeats) {
|
2022-07-27 11:05:52 +02:00
|
|
|
if (!(entity instanceof InventoryHolder))
|
|
|
|
return Transaction.fail();
|
|
|
|
Inventory source = ((InventoryHolder) entity).getInventory();
|
|
|
|
return Transaction.create(() -> {
|
|
|
|
int free = 0;
|
|
|
|
for (ItemStack stack : source.getContents()) {
|
|
|
|
if (stack == null || stack.getType() == Material.AIR) {
|
|
|
|
free++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
2023-04-05 20:35:08 +02:00
|
|
|
return free >= items.size() * repeats;
|
2022-07-27 11:05:52 +02:00
|
|
|
}, () -> {
|
2023-04-05 20:35:08 +02:00
|
|
|
for (int i = 0; i < repeats; i++) {
|
|
|
|
source.addItem(items.stream().map(ItemStack::clone).toArray(ItemStack[]::new));
|
|
|
|
}
|
2022-07-27 11:05:52 +02:00
|
|
|
}, () -> {
|
2023-04-05 20:35:08 +02:00
|
|
|
for (int i = 0; i < repeats; i++) {
|
|
|
|
source.removeItem(items.stream().map(ItemStack::clone).toArray(ItemStack[]::new));
|
|
|
|
}
|
2022-07-27 11:05:52 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-04-05 20:35:08 +02:00
|
|
|
private boolean matches(ItemStack a, ItemStack b) {
|
2023-11-05 13:58:37 +01:00
|
|
|
if (a.getType() != b.getType() || metaFilter.size() > 0 && !metaMatches(a, b, metaFilter))
|
2023-04-05 20:35:08 +02:00
|
|
|
return false;
|
2023-11-05 13:58:37 +01:00
|
|
|
|
2023-04-05 20:35:08 +02:00
|
|
|
if (compareSimilarity && !a.isSimilar(b))
|
|
|
|
return false;
|
2023-11-05 13:58:37 +01:00
|
|
|
|
2023-04-05 20:35:08 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-12-03 11:21:46 +01:00
|
|
|
private boolean metaMatches(ItemStack needle, ItemStack haystack, List<String> meta) {
|
|
|
|
CompoundTag source = NMS.getNBT(needle);
|
|
|
|
CompoundTag compare = NMS.getNBT(haystack);
|
|
|
|
for (String nbt : meta) {
|
|
|
|
String[] parts = nbt.split("\\.");
|
|
|
|
Tag acc = source;
|
|
|
|
Tag cmp = compare;
|
|
|
|
for (int i = 0; i < parts.length; i++) {
|
2023-11-05 13:58:37 +01:00
|
|
|
if (acc == null || cmp == null)
|
2022-12-03 11:21:46 +01:00
|
|
|
return false;
|
|
|
|
if (i < parts.length - 1) {
|
|
|
|
if (!(acc instanceof CompoundTag) || !(cmp instanceof CompoundTag))
|
|
|
|
return false;
|
2023-11-05 13:58:37 +01:00
|
|
|
if (parts[i].equals(acc.getName()) && acc.getName().equals(cmp.getName())) {
|
2022-12-03 11:21:46 +01:00
|
|
|
continue;
|
2023-11-05 13:58:37 +01:00
|
|
|
}
|
2022-12-03 11:21:46 +01:00
|
|
|
acc = ((CompoundTag) acc).getValue().get(parts[i]);
|
|
|
|
cmp = ((CompoundTag) cmp).getValue().get(parts[i]);
|
|
|
|
continue;
|
|
|
|
}
|
2023-11-05 13:58:37 +01:00
|
|
|
if (!acc.getName().equals(parts[i]) || !cmp.getName().equals(parts[i])
|
|
|
|
|| !acc.getValue().equals(cmp.getValue()))
|
2022-12-03 11:21:46 +01:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-07-27 11:05:52 +02:00
|
|
|
@Override
|
2023-04-05 20:35:08 +02:00
|
|
|
public Transaction take(Entity entity, int repeats) {
|
2022-07-27 11:05:52 +02:00
|
|
|
if (!(entity instanceof InventoryHolder))
|
|
|
|
return Transaction.fail();
|
2023-11-05 13:58:37 +01:00
|
|
|
|
2022-07-27 11:05:52 +02:00
|
|
|
Inventory source = ((InventoryHolder) entity).getInventory();
|
2023-11-05 13:58:37 +01:00
|
|
|
return Transaction.create(() -> containsItems(source, repeats, false), () -> {
|
2023-06-18 20:27:13 +02:00
|
|
|
containsItems(source, repeats, true);
|
2022-07-27 11:05:52 +02:00
|
|
|
}, () -> {
|
2023-04-05 20:35:08 +02:00
|
|
|
source.addItem(items.stream().map(ItemStack::clone).toArray(ItemStack[]::new));
|
2022-07-27 11:05:52 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-06-18 19:44:29 +02:00
|
|
|
private boolean tooDamaged(ItemStack toMatch) {
|
|
|
|
if (!requireUndamaged)
|
|
|
|
return false;
|
2023-11-05 13:58:37 +01:00
|
|
|
|
2023-06-18 19:44:29 +02:00
|
|
|
if (SpigotUtil.isUsing1_13API())
|
|
|
|
return toMatch.getItemMeta() instanceof Damageable && ((Damageable) toMatch.getItemMeta()).getDamage() != 0;
|
2023-11-05 13:58:37 +01:00
|
|
|
|
2023-06-18 19:44:29 +02:00
|
|
|
return toMatch.getDurability() == toMatch.getType().getMaxDurability();
|
|
|
|
}
|
|
|
|
|
2022-10-26 09:44:03 +02:00
|
|
|
@Menu(title = "Item editor", dimensions = { 4, 9 })
|
2022-07-27 11:05:52 +02:00
|
|
|
public static class ItemActionEditor extends InventoryMenuPage {
|
|
|
|
private ItemAction base;
|
|
|
|
private Consumer<NPCShopAction> callback;
|
|
|
|
private MenuContext ctx;
|
|
|
|
|
|
|
|
public ItemActionEditor() {
|
|
|
|
}
|
|
|
|
|
|
|
|
public ItemActionEditor(ItemAction base, Consumer<NPCShopAction> callback) {
|
|
|
|
this.base = base;
|
|
|
|
this.callback = callback;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void initialise(MenuContext ctx) {
|
|
|
|
this.ctx = ctx;
|
|
|
|
for (int i = 0; i < 3 * 9; i++) {
|
|
|
|
InventoryMenuSlot slot = ctx.getSlot(i);
|
|
|
|
slot.clear();
|
2022-12-03 11:21:46 +01:00
|
|
|
if (i < base.items.size()) {
|
2022-07-27 11:05:52 +02:00
|
|
|
slot.setItemStack(base.items.get(i).clone());
|
|
|
|
}
|
|
|
|
slot.setClickHandler(event -> {
|
|
|
|
event.setCurrentItem(event.getCursorNonNull());
|
2023-05-11 15:40:01 +02:00
|
|
|
event.setCancelled(true);
|
2022-07-27 11:05:52 +02:00
|
|
|
});
|
|
|
|
}
|
2022-12-03 11:21:46 +01:00
|
|
|
ctx.getSlot(3 * 9 + 1).setItemStack(new ItemStack(Material.ANVIL), "Must have no damage",
|
|
|
|
base.requireUndamaged ? ChatColor.GREEN + "On" : ChatColor.RED + "Off");
|
2023-04-05 20:35:08 +02:00
|
|
|
ctx.getSlot(3 * 9 + 1)
|
2023-11-05 13:58:37 +01:00
|
|
|
.addClickHandler(InputMenus.toggler(res -> base.requireUndamaged = res, base.requireUndamaged));
|
2023-01-05 17:41:47 +01:00
|
|
|
ctx.getSlot(3 * 9 + 2).setItemStack(
|
|
|
|
new ItemStack(Util.getFallbackMaterial("COMPARATOR", "REDSTONE_COMPARATOR")),
|
|
|
|
"Compare item similarity", base.compareSimilarity ? ChatColor.GREEN + "On" : ChatColor.RED + "Off");
|
2023-04-05 20:35:08 +02:00
|
|
|
ctx.getSlot(3 * 9 + 2)
|
2023-11-05 13:58:37 +01:00
|
|
|
.addClickHandler(InputMenus.toggler(res -> base.compareSimilarity = res, base.compareSimilarity));
|
2022-12-03 17:59:16 +01:00
|
|
|
ctx.getSlot(3 * 9 + 3).setItemStack(new ItemStack(Material.BOOK), "NBT comparison filter",
|
|
|
|
Joiner.on("\n").join(base.metaFilter));
|
|
|
|
ctx.getSlot(3 * 9 + 3)
|
2023-11-05 13:58:37 +01:00
|
|
|
.addClickHandler(event -> ctx.getMenu()
|
2022-12-03 17:59:16 +01:00
|
|
|
.transition(InputMenus.stringSetter(() -> Joiner.on(',').join(base.metaFilter),
|
2023-01-05 17:41:47 +01:00
|
|
|
res -> base.metaFilter = res == null ? null : Arrays.asList(res.split(",")))));
|
2022-07-27 11:05:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void onClose(HumanEntity player) {
|
|
|
|
List<ItemStack> items = Lists.newArrayList();
|
|
|
|
for (int i = 0; i < 3 * 9; i++) {
|
|
|
|
if (ctx.getSlot(i).getCurrentItem() != null) {
|
|
|
|
items.add(ctx.getSlot(i).getCurrentItem().clone());
|
|
|
|
}
|
|
|
|
}
|
2022-12-03 11:21:46 +01:00
|
|
|
base.items = items;
|
|
|
|
callback.accept(items.isEmpty() ? null : base);
|
2022-07-27 11:05:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static class ItemActionGUI implements GUI {
|
|
|
|
@Override
|
|
|
|
public InventoryMenuPage createEditor(NPCShopAction previous, Consumer<NPCShopAction> callback) {
|
2022-12-03 11:21:46 +01:00
|
|
|
return new ItemActionEditor(previous == null ? new ItemAction() : (ItemAction) previous, callback);
|
2022-07-27 11:05:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public ItemStack createMenuItem(NPCShopAction previous) {
|
|
|
|
String description = null;
|
|
|
|
if (previous != null) {
|
|
|
|
ItemAction old = (ItemAction) previous;
|
2022-11-28 16:10:50 +01:00
|
|
|
description = old.describe();
|
2022-07-27 11:05:52 +02:00
|
|
|
}
|
|
|
|
return Util.createItem(Material.CHEST, "Item", description);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean manages(NPCShopAction action) {
|
|
|
|
return action instanceof ItemAction;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|