Skip to content

Commit

Permalink
Fix some issues with ShopTrait item costs/results and add NBT filter …
Browse files Browse the repository at this point in the history
…API. Adjust /npc item to be more flexible.
  • Loading branch information
fullwall committed Dec 3, 2022
1 parent c68a7ad commit ede598c
Show file tree
Hide file tree
Showing 57 changed files with 971 additions and 617 deletions.
6 changes: 5 additions & 1 deletion main/src/main/java/net/citizensnpcs/Citizens.java
Expand Up @@ -164,7 +164,11 @@ private void despawnNPCs(boolean save) {
if (registry == null)
continue;
if (save) {
registry.saveToStore();
if (registry == npcRegistry) {
storeNPCs(false);
} else {
registry.saveToStore();
}
}
registry.despawnNPCs(DespawnReason.RELOAD);
}
Expand Down
42 changes: 16 additions & 26 deletions main/src/main/java/net/citizensnpcs/commands/NPCCommands.java
Expand Up @@ -34,7 +34,6 @@
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Horse;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Ocelot;
import org.bukkit.entity.Player;
import org.bukkit.entity.Rabbit;
Expand Down Expand Up @@ -124,6 +123,7 @@
import net.citizensnpcs.trait.Poses;
import net.citizensnpcs.trait.Powered;
import net.citizensnpcs.trait.RabbitType;
import net.citizensnpcs.trait.RotationTrait;
import net.citizensnpcs.trait.ScoreboardTrait;
import net.citizensnpcs.trait.ScriptTrait;
import net.citizensnpcs.trait.SheepTrait;
Expand All @@ -133,7 +133,6 @@
import net.citizensnpcs.trait.SkinLayers.Layer;
import net.citizensnpcs.trait.SkinTrait;
import net.citizensnpcs.trait.SlimeSize;
import net.citizensnpcs.trait.RotationTrait;
import net.citizensnpcs.trait.VillagerProfession;
import net.citizensnpcs.trait.WitherTrait;
import net.citizensnpcs.trait.WolfModifiers;
Expand Down Expand Up @@ -1148,7 +1147,7 @@ public void hurt(CommandContext args, CommandSender sender, NPC npc) {
max = 1,
permission = "citizens.npc.id")
public void id(CommandContext args, CommandSender sender, NPC npc) {
Messaging.send(sender, npc.getId());
sender.sendMessage(Integer.toString(npc.getId()));
}

@Command(
Expand All @@ -1165,38 +1164,29 @@ public void inventory(CommandContext args, CommandSender sender, NPC npc) {

@Command(
aliases = { "npc" },
usage = "item [item] (data)",
usage = "item (item) (amount) (data)",
desc = "Sets the NPC's item",
modifiers = { "item", },
min = 2,
min = 1,
max = 3,
flags = "",
flags = "h",
permission = "citizens.npc.item")
@Requirements(
selected = true,
ownership = true,
types = { EntityType.DROPPED_ITEM, EntityType.ITEM_FRAME, EntityType.FALLING_BLOCK })
public void item(CommandContext args, CommandSender sender, NPC npc, @Arg(1) Material mat) throws CommandException {
if (mat == null)
@Requirements(selected = true, ownership = true)
public void item(CommandContext args, CommandSender sender, NPC npc, @Arg(1) Material mat, @Arg(2) Integer amount,
@Arg(3) Byte data) throws CommandException {
EntityType type = npc.getOrAddTrait(MobType.class).getType();
if (!type.name().contains("ITEM_FRAME") && type != EntityType.DROPPED_ITEM && type != EntityType.FALLING_BLOCK)
throw new CommandException(CommandMessages.REQUIREMENTS_INVALID_MOB_TYPE);
ItemStack stack = args.hasFlag('h') ? ((Player) sender).getItemInHand()
: new ItemStack(mat, amount == null ? 1 : amount, data == null ? 0 : data);
if (mat == null && !args.hasFlag('h'))
throw new CommandException(Messages.UNKNOWN_MATERIAL);
int data = args.getInteger(2, 0);
npc.data().setPersistent(NPC.ITEM_ID_METADATA, mat.name());
npc.data().setPersistent(NPC.ITEM_DATA_METADATA, data);
switch (npc.getEntity().getType()) {
case DROPPED_ITEM:
((org.bukkit.entity.Item) npc.getEntity()).getItemStack().setType(mat);
break;
case ITEM_FRAME:
((ItemFrame) npc.getEntity()).setItem(new ItemStack(mat, 1));
break;
default:
break;
}
npc.setItemProvider(() -> stack);
if (npc.isSpawned()) {
npc.despawn(DespawnReason.PENDING_RESPAWN);
npc.spawn(npc.getStoredLocation(), SpawnReason.RESPAWN);
}
Messaging.sendTr(sender, Messages.ITEM_SET, Util.prettyEnum(mat));
Messaging.sendTr(sender, Messages.ITEM_SET, Util.prettyEnum(stack.getType()));
}

@Command(
Expand Down
Expand Up @@ -36,9 +36,7 @@ public void initialise(MenuContext ctx) {
ConfiguratorInfo info = entry.getValue();
InventoryMenuSlot slot = ctx.getSlot(entry.getKey());
slot.setItemStack(new ItemStack(info.material, 1));
slot.setClickHandler((evt) -> {
info.clickHandler.accept(new ConfiguratorEvent(ctx, npc, slot, evt));
});
slot.setClickHandler((evt) -> info.clickHandler.accept(new ConfiguratorEvent(ctx, npc, slot, evt)));
info.clickHandler.accept(new ConfiguratorEvent(ctx, npc, slot, null));
}
}
Expand Down
Expand Up @@ -91,9 +91,10 @@ public NPC createNPCUsingItem(EntityType type, String name, ItemStack item) {
NPC npc = createNPC(type, name);
if (type == EntityType.DROPPED_ITEM || type == EntityType.FALLING_BLOCK || type == EntityType.GLOW_ITEM_FRAME
|| type == EntityType.ITEM_FRAME) {
npc.data().set(NPC.ITEM_ID_METADATA, item.getType().name());
npc.data().set(NPC.ITEM_DATA_METADATA, (int) item.getData().getData());
npc.data().set(NPC.ITEM_AMOUNT_METADATA, item.getAmount());
npc.data().set(NPC.Metadata.ITEM_AMOUNT, item.getAmount());
npc.data().set(NPC.Metadata.ITEM_ID, item.getType().getId());
npc.data().set(NPC.Metadata.ITEM_DATA, item.getData().getData());
npc.setItemProvider(() -> item);
} else {
throw new UnsupportedOperationException("Not an item entity type");
}
Expand Down
31 changes: 13 additions & 18 deletions main/src/main/java/net/citizensnpcs/trait/ShopTrait.java
Expand Up @@ -73,11 +73,11 @@ public void deleteShop(String name) {
}

public NPCShop getDefaultShop() {
return StoredShops.NPC_SHOPS.computeIfAbsent(npc.getUniqueId().toString(), (s) -> new NPCShop(npc.getName()));
return StoredShops.NPC_SHOPS.computeIfAbsent(npc.getUniqueId().toString(), (s) -> new NPCShop(s));
}

public NPCShop getShop(String name) {
return StoredShops.GLOBAL_SHOPS.computeIfAbsent(name, (s) -> new NPCShop(name));
return StoredShops.GLOBAL_SHOPS.computeIfAbsent(name, (s) -> new NPCShop(s));
}

public void onRightClick(Player player) {
Expand Down Expand Up @@ -121,7 +121,7 @@ public void displayEditor(ShopTrait trait, Player sender) {
}

public String getName() {
return name;
return name == null ? "" : name;
}

public NPCShopPage getOrCreatePage(int page) {
Expand Down Expand Up @@ -178,7 +178,6 @@ public void changePage(int newPage) {

final int idx = i;
slot.setClickHandler(evt -> {
evt.setCancelled(true);
ctx.clearSlots();
NPCShopItem display = item;
if (display == null) {
Expand Down Expand Up @@ -367,20 +366,16 @@ public void initialise(MenuContext ctx) {
NPCShopAction oldCost = modified.cost.stream().filter(template::manages).findFirst().orElse(null);
costItems.getSlots().get(pos)
.setItemStack(Util.editTitle(template.createMenuItem(oldCost), title -> title + " Cost"));
costItems.getSlots().get(pos).setClickHandler(event -> {
event.setCancelled(true);
ctx.getMenu().transition(
template.createEditor(oldCost, cost -> modified.changeCost(template::manages, cost)));
});
costItems.getSlots().get(pos).setClickHandler(event -> ctx.getMenu().transition(
template.createEditor(oldCost, cost -> modified.changeCost(template::manages, cost))));

NPCShopAction oldResult = modified.result.stream().filter(template::manages).findFirst().orElse(null);
actionItems.getSlots().get(pos)
.setItemStack(Util.editTitle(template.createMenuItem(oldResult), title -> title + " Result"));
actionItems.getSlots().get(pos).setClickHandler(event -> {
event.setCancelled(true);
actionItems.getSlots().get(pos).setClickHandler(event ->
ctx.getMenu().transition(template.createEditor(oldResult,
result -> modified.changeResult(template::manages, result)));
});
result -> modified.changeResult(template::manages, result)))
);

pos++;
}
Expand Down Expand Up @@ -530,8 +525,8 @@ public void initialise(MenuContext ctx) {
this.ctx = ctx;
ctx.getSlot(2).setDescription("<f>Edit shop view permission<br>" + shop.getRequiredPermission());
ctx.getSlot(6).setDescription("<f>Edit shop title<br>" + shop.title);
ctx.getSlot(8).setDescription(
"<f>Show shop on right click<br>" + (shop.name != null && shop.name.equals(trait.rightClickShop)));
ctx.getSlot(8)
.setDescription("<f>Show shop on right click<br>" + shop.getName().equals(trait.rightClickShop));
}

@MenuSlot(slot = { 0, 4 }, material = Material.FEATHER, amount = 1, title = "<f>Edit shop items")
Expand Down Expand Up @@ -568,13 +563,13 @@ public void onShopTypeChange(InventoryMenuSlot slot, CitizensInventoryClickEvent
@MenuSlot(slot = { 0, 8 }, material = Material.COMMAND_BLOCK, amount = 1)
public void onToggleRightClick(InventoryMenuSlot slot, CitizensInventoryClickEvent event) {
event.setCancelled(true);
if (shop.name != null && shop.name.equals(trait.rightClickShop)) {
if (shop.getName().equals(trait.rightClickShop)) {
trait.rightClickShop = null;
} else {
trait.rightClickShop = shop.name;
}
ctx.getSlot(8).setDescription(
"<f>Show shop on right click<br>" + (shop.name != null && shop.name.equals(trait.rightClickShop)));
ctx.getSlot(8)
.setDescription("<f>Show shop on right click<br>" + (shop.getName().equals(trait.rightClickShop)));
}
}

Expand Down
106 changes: 79 additions & 27 deletions main/src/main/java/net/citizensnpcs/trait/shop/ItemAction.java
Expand Up @@ -2,7 +2,6 @@

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
Expand All @@ -19,17 +18,25 @@
import com.google.common.collect.Lists;

import net.citizensnpcs.api.gui.BooleanSlotHandler;
import net.citizensnpcs.api.gui.InputMenus;
import net.citizensnpcs.api.gui.InventoryMenuPage;
import net.citizensnpcs.api.gui.InventoryMenuSlot;
import net.citizensnpcs.api.gui.Menu;
import net.citizensnpcs.api.gui.MenuContext;
import net.citizensnpcs.api.jnbt.CompoundTag;
import net.citizensnpcs.api.jnbt.Tag;
import net.citizensnpcs.api.persistence.Persist;
import net.citizensnpcs.util.NMS;
import net.citizensnpcs.util.Util;

public class ItemAction extends NPCShopAction {
@Persist
public boolean compareSimilarity = false;
@Persist
public List<ItemStack> items = Lists.newArrayList();
@Persist
public List<String> metaFilter = Lists.newArrayList();
@Persist
public boolean requireUndamaged = true;

public ItemAction() {
Expand All @@ -44,29 +51,38 @@ public ItemAction(List<ItemStack> items) {
}

private boolean containsItems(Inventory source, BiFunction<ItemStack, Integer, ItemStack> filter) {
Map<Material, Integer> required = items.stream()
.collect(Collectors.toMap(k -> k.getType(), v -> v.getAmount()));
List<Integer> req = items.stream().map(i -> i.getAmount()).collect(Collectors.toList());
ItemStack[] contents = source.getContents();
for (int i = 0; i < contents.length; i++) {
ItemStack stack = contents[i];
if (stack == null || stack.getType() == Material.AIR || !required.containsKey(stack.getType()))
if (stack == null || stack.getType() == Material.AIR)
continue;
if (requireUndamaged && stack.getItemMeta() instanceof Damageable
&& ((Damageable) stack.getItemMeta()).getDamage() != 0)
continue;
int remaining = required.remove(stack.getType());
int taken = stack.getAmount() > remaining ? remaining : stack.getAmount();
ItemStack res = filter.apply(stack, taken);
if (res == null) {
source.clear(i);
} else {
source.setItem(i, res);
}
if (remaining - taken > 0) {
required.put(stack.getType(), remaining - taken);
for (int j = 0; j < items.size(); j++) {
ItemStack match = items.get(j);
if (req.get(j) <= 0)
continue;
if (match.getType() != stack.getType())
continue;
if (metaFilter.size() > 0 && !metaMatches(match, stack, metaFilter))
continue;
if (compareSimilarity && !match.isSimilar(stack))
continue;

int remaining = req.get(j);
int taken = stack.getAmount() > remaining ? remaining : stack.getAmount();
ItemStack res = filter.apply(stack, taken);
if (res == null) {
source.clear(i);
} else {
source.setItem(i, res);
}
req.set(j, remaining - taken);
}
}
return required.size() == 0;
return req.stream().collect(Collectors.summingInt(n -> n)) <= 0;
}

@Override
Expand Down Expand Up @@ -104,19 +120,43 @@ public Transaction grant(Entity entity) {
});
}

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++) {
if (acc == null)
return false;
if (cmp == null)
return false;
if (i < parts.length - 1) {
if (!(acc instanceof CompoundTag) || !(cmp instanceof CompoundTag))
return false;
if (parts[i].equals(acc.getName()) && acc.getName().equals(cmp.getName()))
continue;
acc = ((CompoundTag) acc).getValue().get(parts[i]);
cmp = ((CompoundTag) cmp).getValue().get(parts[i]);
continue;
}
if (!acc.getName().equals(parts[i]) || !cmp.getName().equals(parts[i]))
return false;
if (!acc.getValue().equals(cmp.getValue()))
return false;
}
}
return true;
}

@Override
public Transaction take(Entity entity) {
if (!(entity instanceof InventoryHolder))
return Transaction.fail();
Inventory source = ((InventoryHolder) entity).getInventory();
return Transaction.create(() -> {
boolean contains = containsItems(source, (s, t) -> s);
for (ItemStack item : items) {
if (item.hasItemMeta() && !source.contains(item)) {
contains = false;
}
}
return contains;
return containsItems(source, (stack, taken) -> stack);
}, () -> {
containsItems(source, (stack, taken) -> {
if (stack.getAmount() == taken) {
Expand Down Expand Up @@ -151,19 +191,30 @@ public void initialise(MenuContext ctx) {
for (int i = 0; i < 3 * 9; i++) {
InventoryMenuSlot slot = ctx.getSlot(i);
slot.clear();
if (base != null && i < base.items.size()) {
if (i < base.items.size()) {
slot.setItemStack(base.items.get(i).clone());
}
slot.setClickHandler(event -> {
event.setCancelled(true);
event.setCurrentItem(event.getCursorNonNull());
});
}
ctx.getSlot(3 * 9 + 1).setItemStack(new ItemStack(Material.ANVIL), "Must have no damage");

ctx.getSlot(3 * 9 + 1).setItemStack(new ItemStack(Material.ANVIL), "Must have no damage",
base.requireUndamaged ? ChatColor.GREEN + "On" : ChatColor.RED + "Off");
ctx.getSlot(3 * 9 + 1).addClickHandler(new BooleanSlotHandler((res) -> {
base.requireUndamaged = res;
return res ? ChatColor.GREEN + "On" : ChatColor.RED + "Off";
}, base == null ? false : base.requireUndamaged));
}, base.requireUndamaged));
ctx.getSlot(3 * 9 + 2).setItemStack(new ItemStack(Material.COMPARATOR), "Compare item similarity",
base.compareSimilarity ? ChatColor.GREEN + "On" : ChatColor.RED + "Off");
ctx.getSlot(3 * 9 + 2).addClickHandler(new BooleanSlotHandler((res) -> {
base.compareSimilarity = res;
return res ? ChatColor.GREEN + "On" : ChatColor.RED + "Off";
}, base.compareSimilarity));
ctx.getSlot(3 * 9 + 3).setItemStack(new ItemStack(Material.BOOK), "NBT comparison filter");
ctx.getSlot(3 * 9 + 3).addClickHandler((event) -> ctx.getMenu()
.transition(InputMenus.stringSetter(() -> "", res -> base.metaFilter = Lists.newArrayList(res))));
}

@Override
Expand All @@ -174,14 +225,15 @@ public void onClose(HumanEntity player) {
items.add(ctx.getSlot(i).getCurrentItem().clone());
}
}
callback.accept(items.isEmpty() ? null : new ItemAction(items));
base.items = items;
callback.accept(items.isEmpty() ? null : base);
}
}

public static class ItemActionGUI implements GUI {
@Override
public InventoryMenuPage createEditor(NPCShopAction previous, Consumer<NPCShopAction> callback) {
return new ItemActionEditor(previous == null ? new ItemAction() : null, callback);
return new ItemActionEditor(previous == null ? new ItemAction() : (ItemAction) previous, callback);
}

@Override
Expand Down

0 comments on commit ede598c

Please sign in to comment.