diff --git a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java index 287446cd324..a191e195cb3 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Essentials.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Essentials.java @@ -191,6 +191,8 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials { EconomyLayers.init(); } + private ItemGroups itemGroups; + public Essentials() { } @@ -358,6 +360,10 @@ public void onEnable() { confList.add(jails); execTimer.mark("Init(Jails)"); + itemGroups = new ItemGroups(this); + confList.add(itemGroups); + execTimer.mark("Init(ItemGroups)"); + EconomyLayers.onEnable(this); //Spawner item provider only uses one but it's here for legacy... diff --git a/Essentials/src/main/java/com/earth2me/essentials/ItemGroupQuery.java b/Essentials/src/main/java/com/earth2me/essentials/ItemGroupQuery.java new file mode 100644 index 00000000000..06e2a12efe4 --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/ItemGroupQuery.java @@ -0,0 +1,43 @@ +package com.earth2me.essentials; + +import net.ess3.api.IEssentials; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Tag; + +public class ItemGroupQuery { + + private final String itemGroup; + private final int amount; + + public ItemGroupQuery(String group, int quantity) { + this.itemGroup = group; + this.amount = quantity; + } + + public boolean contains(IEssentials ess, Material item){ + Tag tag = Bukkit.getTag(Tag.REGISTRY_ITEMS, NamespacedKey.minecraft(itemGroup), Material.class); + if (tag != null && tag.isTagged(item)) { + return true; + } + final ItemGroups groupsConfig = new ItemGroups(ess); + return groupsConfig.getItemGroup(itemGroup).contains(item); + } + + public String getItemGroup() { + return itemGroup; + } + + public int getAmount() { + return amount; + } + + @Override + public String toString() { + return "ItemGroupQuery{" + + "itemGroup='" + itemGroup + '\'' + + ", quantity=" + amount + + '}'; + } +} diff --git a/Essentials/src/main/java/com/earth2me/essentials/ItemGroups.java b/Essentials/src/main/java/com/earth2me/essentials/ItemGroups.java new file mode 100644 index 00000000000..193ce9220f3 --- /dev/null +++ b/Essentials/src/main/java/com/earth2me/essentials/ItemGroups.java @@ -0,0 +1,48 @@ +package com.earth2me.essentials; + +import com.earth2me.essentials.config.EssentialsConfiguration; +import net.ess3.api.IEssentials; +import net.ess3.api.IItemGroups; +import org.bukkit.Material; + +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +public class ItemGroups implements IItemGroups { + + private final EssentialsConfiguration config; + private final Map> itemGroups = new HashMap<>(); + + public ItemGroups(final IEssentials ess) { + this.config = new EssentialsConfiguration(new File(ess.getDataFolder(), "groups.yml")); + reloadConfig(); + } + + @Override + public void reloadConfig() { + synchronized (itemGroups) { + config.load(); + itemGroups.clear(); + + for (String id : config.getKeys()) { + final List list = config.getList(id, String.class); + itemGroups.put(id, list.stream().map(Material::matchMaterial).collect(Collectors.toList())); + } + } + } + + @Override + public List getItemGroup(String group) { + return itemGroups.getOrDefault(group, Collections.emptyList()); + } + + @Override + public Set getItemGroups() { + return itemGroups.keySet(); + } +} diff --git a/Essentials/src/main/java/com/earth2me/essentials/Trade.java b/Essentials/src/main/java/com/earth2me/essentials/Trade.java index c82bf51cbb6..c28d6835763 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/Trade.java +++ b/Essentials/src/main/java/com/earth2me/essentials/Trade.java @@ -31,39 +31,45 @@ public class Trade { private final transient Trade fallbackTrade; private final transient BigDecimal money; private final transient ItemStack itemStack; + private final transient ItemGroupQuery itemGroupQuery; private final transient Integer exp; private final transient IEssentials ess; public Trade(final String command, final IEssentials ess) { - this(command, null, null, null, null, ess); + this(command, null, null, null, null, null, ess); } public Trade(final String command, final Trade fallback, final IEssentials ess) { - this(command, fallback, null, null, null, ess); + this(command, fallback, null, null, null, null, ess); } @Deprecated public Trade(final double money, final com.earth2me.essentials.IEssentials ess) { - this(null, null, BigDecimal.valueOf(money), null, null, (IEssentials) ess); + this(null, null, BigDecimal.valueOf(money), null, null, null, (IEssentials) ess); } public Trade(final BigDecimal money, final IEssentials ess) { - this(null, null, money, null, null, ess); + this(null, null, money, null, null, null, ess); } public Trade(final ItemStack items, final IEssentials ess) { - this(null, null, null, items, null, ess); + this(null, null, null, items, null, null, ess); } public Trade(final int exp, final IEssentials ess) { - this(null, null, null, null, exp, ess); + this(null, null, null, null, null, exp, ess); } - private Trade(final String command, final Trade fallback, final BigDecimal money, final ItemStack item, final Integer exp, final IEssentials ess) { + public Trade(final ItemGroupQuery query, final IEssentials ess){ + this(null, null, null, null, query, null, ess); + } + + private Trade(final String command, final Trade fallback, final BigDecimal money, final ItemStack item, final ItemGroupQuery groupQuery, final Integer exp, final IEssentials ess) { this.command = command; this.fallbackTrade = fallback; this.money = money; this.itemStack = item; + this.itemGroupQuery = groupQuery; this.exp = exp; this.ess = ess; } @@ -146,7 +152,7 @@ public static void log(final String type, final String subtype, final String eve sb.append(loc.getBlockY()).append(","); sb.append(loc.getBlockZ()).append(","); } - + if (endBalance == null) { sb.append(","); } else { @@ -303,6 +309,17 @@ public void charge(final IUser user, final CompletableFuture future) { Inventories.removeItemAmount(user.getBase(), getItemStack(), getItemStack().getAmount()); user.getBase().updateInventory(); } + if(getItemGroupQuery() != null){ + if(ess.getSettings().isDebug()) { + ess.getLogger().log(Level.INFO, "charging user " + user.getName() + " itemgroup " +getItemGroupQuery().toString()); + } + if (!Inventories.containsAtLeast(user.getBase(), ess, getItemGroupQuery())) { + future.completeExceptionally(new ChargeException(tl("missingItems", getItemGroupQuery().getAmount(), "~"+getItemGroupQuery().getItemGroup().toLowerCase(Locale.ENGLISH).replace("_", " ")))); + return; + } + Inventories.removeItemAmount(user.getBase(), ess, getItemGroupQuery()); + user.getBase().updateInventory(); + } if (command != null) { final BigDecimal cost = getCommandCost(user); if (!user.canAfford(cost) && cost.signum() > 0) { @@ -335,6 +352,10 @@ public ItemStack getItemStack() { return itemStack; } + public ItemGroupQuery getItemGroupQuery() { + return itemGroupQuery; + } + public Integer getExperience() { return exp; } diff --git a/Essentials/src/main/java/com/earth2me/essentials/craftbukkit/Inventories.java b/Essentials/src/main/java/com/earth2me/essentials/craftbukkit/Inventories.java index eb5c395bff8..be462b34565 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/craftbukkit/Inventories.java +++ b/Essentials/src/main/java/com/earth2me/essentials/craftbukkit/Inventories.java @@ -1,7 +1,10 @@ package com.earth2me.essentials.craftbukkit; +import com.earth2me.essentials.ItemGroupQuery; +import com.earth2me.essentials.ItemGroups; import com.earth2me.essentials.utils.MaterialUtil; import com.earth2me.essentials.utils.VersionUtil; +import net.ess3.api.IEssentials; import org.bukkit.entity.Player; import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.Inventory; @@ -85,6 +88,22 @@ public static boolean containsAtLeast(final Player player, final ItemStack item, return false; } + public static boolean containsAtLeast(final Player player, IEssentials ess, final ItemGroupQuery query) { + int amount = query.getAmount(); + for (final ItemStack invItem : player.getInventory().getContents()) { + if (isEmpty(invItem)) { + continue; + } + if (query.contains(ess, invItem.getType())) { + amount -= invItem.getAmount(); + if (amount <= 0) { + return true; + } + } + } + return false; + } + public static boolean hasSpace(final Player player, final int maxStack, final boolean includeArmor, ItemStack... items) { items = normalizeItems(cloneItems(items)); final InventoryData inventoryData = parseInventoryData(player.getInventory(), items, maxStack, includeArmor); @@ -276,6 +295,41 @@ public static boolean removeItemAmount(final Player player, final ItemStack toRe return false; } + public static boolean removeItemAmount(final Player player, final IEssentials ess, final ItemGroupQuery query) { + final List clearSlots = new ArrayList<>(); + final ItemStack[] items = player.getInventory().getContents(); + + int amount = query.getAmount(); + for (int i = 0; i < items.length; i++) { + final ItemStack item = items[i]; + if (isEmpty(item)) { + continue; + } + + if (query.contains(ess, item.getType())) { + if (item.getAmount() >= amount) { + item.setAmount(item.getAmount() - amount); + player.getInventory().setItem(i, item); + for (final int slot : clearSlots) { + clearSlot(player, slot); + } + return true; + } else { + amount -= item.getAmount(); + clearSlots.add(i); + } + + if (amount == 0) { + for (final int slot : clearSlots) { + clearSlot(player, slot); + } + return true; + } + } + } + return false; + } + public static void clearSlot(final Player player, final int slot) { final ItemStack item = player.getInventory().getItem(slot); if (!isEmpty(item)) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/signs/EssentialsSign.java b/Essentials/src/main/java/com/earth2me/essentials/signs/EssentialsSign.java index cb0aaaf96b2..73f83ad813f 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/signs/EssentialsSign.java +++ b/Essentials/src/main/java/com/earth2me/essentials/signs/EssentialsSign.java @@ -2,6 +2,7 @@ import com.earth2me.essentials.ChargeException; import com.earth2me.essentials.CommandSource; +import com.earth2me.essentials.ItemGroupQuery; import com.earth2me.essentials.MetaItemStack; import com.earth2me.essentials.Trade; import com.earth2me.essentials.User; @@ -338,6 +339,12 @@ protected final void validateTrade(final ISign sign, final int amountIndex, fina sign.setLine(itemIndex, "exp"); return; } + if(itemType.startsWith("~")){ + final int amount = getIntegerPositive(getSignText(sign, amountIndex)); + sign.setLine(amountIndex, Integer.toString(amount)); + sign.setLine(itemIndex, "§6~§r"+itemType.substring(1)); + return; + } final Trade trade = getTrade(sign, amountIndex, itemIndex, player, ess); final ItemStack item = trade.getItemStack(); sign.setLine(amountIndex, Integer.toString(item.getAmount())); @@ -354,6 +361,11 @@ protected final Trade getTrade(final ISign sign, final int amountIndex, final in final int amount = getIntegerPositive(getSignText(sign, amountIndex)); return new Trade(amount, ess); } + if(itemType.startsWith("§6~§r")){ + final int amount = Math.min(getIntegerPositive(getSignText(sign, amountIndex)), 64 * player.getBase().getInventory().getSize()); + final ItemGroupQuery query = new ItemGroupQuery(itemType.substring(5), amount); + return new Trade(query, ess); + } final ItemStack item = getItemStack(itemType, 1, allowId, ess); final int amount = Math.min(getIntegerPositive(getSignText(sign, amountIndex)), item.getType().getMaxStackSize() * player.getBase().getInventory().getSize()); if (item.getType() == Material.AIR || amount < 1) { diff --git a/Essentials/src/main/java/com/earth2me/essentials/signs/SignSell.java b/Essentials/src/main/java/com/earth2me/essentials/signs/SignSell.java index 1e1079f2588..f4f81867bce 100644 --- a/Essentials/src/main/java/com/earth2me/essentials/signs/SignSell.java +++ b/Essentials/src/main/java/com/earth2me/essentials/signs/SignSell.java @@ -1,6 +1,7 @@ package com.earth2me.essentials.signs; import com.earth2me.essentials.ChargeException; +import com.earth2me.essentials.ItemGroupQuery; import com.earth2me.essentials.Trade; import com.earth2me.essentials.Trade.OverflowType; import com.earth2me.essentials.User; @@ -30,13 +31,24 @@ protected boolean onSignInteract(final ISign sign, final User player, final Stri // Check if the player is trying to sell in bulk. if (ess.getSettings().isAllowBulkBuySell() && player.getBase().isSneaking()) { final ItemStack heldItem = player.getItemInHand(); - if (charge.getItemStack().isSimilar(heldItem)) { + if (charge.getItemStack() != null && charge.getItemStack().isSimilar(heldItem)) { final int initialItemAmount = charge.getItemStack().getAmount(); final int newItemAmount = heldItem.getAmount(); final ItemStack item = charge.getItemStack(); item.setAmount(newItemAmount); charge = new Trade(item, ess); + final BigDecimal chargeAmount = money.getMoney(); + //noinspection BigDecimalMethodWithoutRoundingCalled + BigDecimal pricePerSingleItem = chargeAmount.divide(new BigDecimal(initialItemAmount)); + pricePerSingleItem = pricePerSingleItem.multiply(new BigDecimal(newItemAmount)); + money = new Trade(pricePerSingleItem, ess); + }else if(charge.getItemGroupQuery() != null && charge.getItemGroupQuery().contains(ess, heldItem.getType())){ + final ItemGroupQuery groupQuery = charge.getItemGroupQuery(); + final int initialItemAmount = groupQuery.getAmount(); + final int newItemAmount = heldItem.getAmount(); + charge = new Trade(new ItemGroupQuery(groupQuery.getItemGroup(), newItemAmount), ess); + final BigDecimal chargeAmount = money.getMoney(); //noinspection BigDecimalMethodWithoutRoundingCalled BigDecimal pricePerSingleItem = chargeAmount.divide(new BigDecimal(initialItemAmount)); diff --git a/Essentials/src/main/java/net/ess3/api/IItemGroups.java b/Essentials/src/main/java/net/ess3/api/IItemGroups.java new file mode 100644 index 00000000000..15ab907a48a --- /dev/null +++ b/Essentials/src/main/java/net/ess3/api/IItemGroups.java @@ -0,0 +1,26 @@ +package net.ess3.api; + +import com.earth2me.essentials.IConf; +import org.bukkit.Material; + +import java.util.List; +import java.util.Set; + +/** + * Provides access to the storage of item groups. Maintainers should add methods to this interface. + */ +public interface IItemGroups extends IConf { + + /** + * Gets the set of saved item groups from config file + * @return the set of item groups + */ + Set getItemGroups(); + + /** + * Gets the list of item materials inside an item group + * @param group the item group + * @return a list of items in the item group + */ + List getItemGroup(String group); +} diff --git a/Essentials/src/main/resources/groups.yml b/Essentials/src/main/resources/groups.yml new file mode 100644 index 00000000000..a8b7907118b --- /dev/null +++ b/Essentials/src/main/resources/groups.yml @@ -0,0 +1,13 @@ +#EssentialsX item group configuration +#Item groups are currently used only by sell sign +#By creating a group here and using ~[group_id] on the sign +#Any player will be able to sell items with one of the types written here + +#You can use minecraft tags as groups, for reference https://minecraft.fandom.com/wiki/Tag#Item_tags +#Example +#dirt: +# - DIRT +# - GRASS +# - PODZOL +# - COARSE_DIRT +# - ROOTED_DIRT \ No newline at end of file