diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java b/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java index 24326163..ac6db881 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/IItemSource.java @@ -6,20 +6,28 @@ import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.inventory.IInventory; import net.minecraft.item.ItemStack; +import javax.annotation.Nonnull; +import java.util.Map; import java.util.function.Predicate; public interface IItemSource { - int take(Predicate predicate, boolean simulate, int count); + /** + * We are probably not going to need to get a lot of items from this method + * so I don't care about the overhead of storing all fetched items even if downstream code doesn't need it + */ + @Nonnull + Map take(Predicate predicate, boolean simulate, int count); - default boolean takeOne(Predicate predicate, boolean simulate) { - return take(predicate, simulate, 1) == 1; + default ItemStack takeOne(Predicate predicate, boolean simulate) { + Map take = take(predicate, simulate, 1); + return take.isEmpty() ? null : take.keySet().iterator().next(); } default boolean takeAll(Predicate predicate, boolean simulate, int count) { // fast path for 1 item take requests - if (count == 1) return takeOne(predicate, simulate); - int have = take(predicate, true, count); - if (have < count) return false; + if (count == 1) return takeOne(predicate, simulate) != null; + Map have = take(predicate, true, count); + if (have.values().stream().mapToInt(Integer::intValue).sum() < count) return false; take(predicate, simulate, count); return true; } diff --git a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java index 946ed7d9..e82f9323 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/structure/StructureUtility.java @@ -89,7 +89,7 @@ public class StructureUtility { if (check(o, world, x, y, z)) return PlaceResult.ACCEPT; // user should place anything here. // maybe make this configurable, but for now we try to take some cobble from user - if (s.takeOne(ItemStackPredicate.from(Blocks.cobblestone), false)) { + if (s.takeOne(ItemStackPredicate.from(Blocks.cobblestone), false) != null) { world.setBlock(x, y, z, Blocks.cobblestone, 0, 2); } return PlaceResult.REJECT; @@ -134,7 +134,7 @@ public class StructureUtility { return PlaceResult.REJECT; Item itemBlock = Item.getItemFromBlock(block); int itemMeta = itemBlock instanceof ISpecialItemBlock ? ((ISpecialItemBlock) itemBlock).getItemMetaFromBlockMeta(block, meta) : meta; - if (!s.takeOne(ItemStackPredicate.from(itemBlock).withMeta(itemMeta), false)) + if (s.takeOne(ItemStackPredicate.from(itemBlock).withMeta(itemMeta), false) == null) return PlaceResult.REJECT; if (block instanceof ICustomBlockSetting) { ICustomBlockSetting block2 = (ICustomBlockSetting) block; diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java index ef03bf1f..db5a20d9 100644 --- a/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java +++ b/src/main/java/com/gtnewhorizon/structurelib/util/InventoryUtility.java @@ -18,12 +18,11 @@ */ package com.gtnewhorizon.structurelib.util; -import com.gtnewhorizon.structurelib.util.ItemStackArrayIterable; -import com.gtnewhorizon.structurelib.util.SortedRegistry; import net.minecraft.entity.player.EntityPlayerMP; import net.minecraft.item.ItemStack; import java.util.Iterator; +import java.util.Map; import java.util.NavigableMap; import java.util.TreeMap; import java.util.function.Function; @@ -51,14 +50,15 @@ public class InventoryUtility { * @param count let's hope int size is enough... * @return amount taken. never negative nor bigger than count... */ - public static int takeFromInventory(EntityPlayerMP player, Predicate predicate, boolean simulate, int count) { - int found = 0; + public static Map takeFromInventory(EntityPlayerMP player, Predicate predicate, boolean simulate, int count) { + Map store = new ItemStackMap<>(); + int sum = 0; for (Function> provider : inventoryProviders) { - found += takeFromInventory(provider.apply(player), predicate, simulate, count - found); - if (found >= count) - return found; + sum += takeFromInventory(provider.apply(player), predicate, simulate, count - sum, true, store); + if (sum >= count) + return store; } - return found; + return store; } /** @@ -71,11 +71,13 @@ public class InventoryUtility { * @param count let's hope int size is enough... * @return amount taken. never negative nor bigger than count... */ - public static int takeFromInventory(Iterable inv, Predicate predicate, boolean simulate, int count) { - return takeFromInventory(inv, predicate, simulate, count, true); + public static Map takeFromInventory(Iterable inv, Predicate predicate, boolean simulate, int count) { + Map store = new ItemStackMap<>(); + takeFromInventory(inv, predicate, simulate, count, true, store); + return store; } - private static int takeFromInventory(Iterable inv, Predicate predicate, boolean simulate, int count, boolean recurse) { + private static int takeFromInventory(Iterable inv, Predicate predicate, boolean simulate, int count, boolean recurse, Map store) { int found = 0; for (Iterator iterator = inv.iterator(); iterator.hasNext(); ) { ItemStack stack = iterator.next(); @@ -87,10 +89,13 @@ public class InventoryUtility { if (found > count) { if (!simulate) { // leave the surplus in its place - stack.stackSize = found - count; + int surplus = found - count; + store.merge(stack, stack.stackSize - surplus, Integer::sum); + stack.stackSize = surplus; } return count; } + store.merge(stack, stack.stackSize, Integer::sum); if (!simulate) iterator.remove(); if (found == count) @@ -100,7 +105,7 @@ public class InventoryUtility { for (Function> f : stackExtractors) { Iterable stacks = f.apply(stack); if (stacks == null) continue; - found += takeFromInventory(inv, predicate, simulate, count - found, false); + found += takeFromInventory(inv, predicate, simulate, count - found, false, store); if (found > count) return found; } } diff --git a/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackMap.java b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackMap.java new file mode 100644 index 00000000..81d93fd6 --- /dev/null +++ b/src/main/java/com/gtnewhorizon/structurelib/util/ItemStackMap.java @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2021, glee8e, Code Chicken. + * + * This file is originally part of NotEnoughtItem by Code Chicken. + * It is adapted to implement the standard Map interface by glease + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 2.1 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ +package com.gtnewhorizon.structurelib.util; +import com.google.common.base.Objects; +import com.google.common.collect.Iterators; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.AbstractMap; +import java.util.AbstractSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import static net.minecraftforge.oredict.OreDictionary.WILDCARD_VALUE; + +/** + * A map class for ItemStack keys with wildcard damage/NBT. Optimised for lookup. + *

+ * This map does NOT support null values or null keys! null will be silently ignored! + *

+ * Originally created by CodeChicken for NotEnoughItems. Adapted to {@code Map} interface by glee8e + * @author CodeChiken + * @author glee8e + */ +public final class ItemStackMap extends AbstractMap { + public static final NBTTagCompound WILDCARD_TAG; + + static { + WILDCARD_TAG = new NBTTagCompound(); + WILDCARD_TAG.setBoolean("*", true); + } + + private final HashMap itemMap = new HashMap<>(); + private int size; + + static ItemStack newItemStack(Item item, int size, int damage, NBTTagCompound tag) { + ItemStack stack = new ItemStack(item, size, damage); + stack.stackTagCompound = tag; + return stack; + } + + static int actualDamage(ItemStack stack) { + return Items.diamond.getDamage(stack); + } + + static int getKeyType(int damage, NBTTagCompound tag) { + int i = 0; + if (isWildcard(damage)) i = 1; + if (isWildcard(tag)) i |= 2; + return i; + } + + static ItemStack wildcard(Item item) { + return newItemStack(item, 1, WILDCARD_VALUE, WILDCARD_TAG); + } + + static boolean isWildcard(int damage) { + return damage == WILDCARD_VALUE; + } + + static boolean isWildcard(NBTTagCompound tag) { + return tag != null && tag.getBoolean("*"); + } + + @Override + public T get(Object key) { + if (!(key instanceof ItemStack)) return null; + ItemStack stack = ((ItemStack) key); + if (stack.getItem() == null) return null; + DetailMap map = itemMap.get(stack.getItem()); + return map == null ? null : map.get(stack); + } + + @Override + public T put(ItemStack key, T value) { + if (key == null || key.getItem() == null || value == null) + return null; + return itemMap.computeIfAbsent(key.getItem(), k -> new DetailMap()).put(key, value); + } + + @Override + public T remove(Object key) { + if (!(key instanceof ItemStack)) return null; + ItemStack stack = ((ItemStack) key); + if (stack.getItem() == null) return null; + DetailMap map = itemMap.get(stack.getItem()); + return map == null ? null : map.remove(stack); + } + + @Override + public boolean containsKey(Object key) { + if (!(key instanceof ItemStack)) return false; + ItemStack stack = ((ItemStack) key); + if (stack.getItem() == null) return false; + DetailMap map = itemMap.get(stack.getItem()); + return map != null && map.get(stack) != null; + } + + @Override + public void clear() { + itemMap.clear(); + size = 0; + } + + @Override + public Set> entrySet() { + return new SetView(); + } + + private static class StackMetaKey { + public final int damage; + public final NBTTagCompound tag; + + public StackMetaKey(int damage, NBTTagCompound tag) { + this.damage = damage; + this.tag = tag; + } + + public StackMetaKey(ItemStack key) { + this(actualDamage(key), key.stackTagCompound); + } + + public int hashCode() { + return Objects.hashCode(damage, tag); + } + + public boolean equals(Object o) { + if (!(o instanceof StackMetaKey)) + return false; + StackMetaKey t = (StackMetaKey) o; + return damage == t.damage && Objects.equal(tag, t.tag); + } + } + + private static class DetailIter implements Iterator> { + private final Item owner; + private final ItemStackMap.DetailMap backing; + @Nullable + private final Iterator> damageIter; + @Nullable + private final Iterator> tagIter; + @Nullable + private final Iterator> metaIter; + private DetailIterState state = DetailIterState.NOT_STARTED; + private boolean removed = false; + private DetailIter(Map.Entry.DetailMap> input) { + this.owner = input.getKey(); + this.backing = input.getValue(); + damageIter = backing.damageMap != null ? backing.damageMap.entrySet().iterator() : null; + tagIter = backing.tagMap != null ? backing.tagMap.entrySet().iterator() : null; + metaIter = backing.metaMap != null ? backing.metaMap.entrySet().iterator() : null; + } + + private DetailIterState nextState() { + switch (state) { + case NOT_STARTED: + if (backing.hasWildcard) return DetailIterState.WILDCARD; + case WILDCARD: + case DAMAGE: + if (damageIter != null && damageIter.hasNext()) return DetailIterState.DAMAGE; + case TAG: + if (tagIter != null && tagIter.hasNext()) return DetailIterState.TAG; + case META: + if (metaIter != null && metaIter.hasNext()) return DetailIterState.META; + case DONE: + return DetailIterState.DONE; + default: + throw new IllegalStateException("Unexpected value: " + state); + } + } + + @Override + public void remove() { + if (removed) + throw new IllegalStateException("remove() called twice"); + state.remove(this); + removed = true; + } + + @Override + public boolean hasNext() { + return nextState() != DetailIterState.DONE; + } + + @Override + public Map.Entry next() { + DetailIterState nextState = nextState(); + if (nextState == DetailIterState.DONE) + throw new NoSuchElementException(); + state = nextState; + removed = false; + return nextState.get(this); + } + + private enum DetailIterState { + NOT_STARTED { + @Override + Map.Entry get(DetailIter iter) { + throw new AssertionError("Should not call get on NOT_STARTED"); + } + + @Override + void remove(DetailIter iter) { + throw new IllegalStateException("next() never called"); + } + }, + WILDCARD { + @Override + Map.Entry get(DetailIter iter) { + return new Map.Entry() { + private final ItemStack key = wildcard(iter.owner); + @Override + public ItemStack getKey() { + return key; + } + + @Override + public T getValue() { + return iter.backing.wildcard; + } + + @Override + public T setValue(T value) { + return iter.backing.wildcard = value; + } + }; + } + + @Override + void remove(DetailIter iter) { + assert iter.backing.hasWildcard; + iter.backing.wildcard = null; + iter.backing.hasWildcard = false; + } + }, + DAMAGE { + @Override + Map.Entry get(DetailIter iter) { + assert iter.damageIter != null; + Map.Entry entry = iter.damageIter.next(); + return new ItemStackEntry<>(newItemStack(iter.owner, 1, entry.getKey(), WILDCARD_TAG), entry); + } + + @Override + void remove(DetailIter iter) { + assert iter.damageIter != null; + iter.damageIter.remove(); + } + }, + TAG { + @Override + Map.Entry get(DetailIter iter) { + assert iter.tagIter != null; + Map.Entry entry = iter.tagIter.next(); + return new ItemStackEntry<>(newItemStack(iter.owner, 1, WILDCARD_VALUE, entry.getKey()), entry); + } + + @Override + void remove(DetailIter iter) { + assert iter.tagIter != null; + iter.tagIter.remove(); + } + }, + META { + @Override + Map.Entry get(DetailIter iter) { + assert iter.metaIter != null; + Map.Entry entry = iter.metaIter.next(); + return new ItemStackEntry<>(newItemStack(iter.owner, 1, entry.getKey().damage, entry.getKey().tag), entry); + } + + @Override + void remove(DetailIter iter) { + assert iter.metaIter != null; + iter.metaIter.remove(); + } + }, + DONE { + @Override + Map.Entry get(DetailIter iter) { + throw new AssertionError("Should not call get on DONE"); + } + + @Override + void remove(DetailIter iter) { + throw new AssertionError("Should not call remove on DONE"); + } + }; + + abstract Map.Entry get(DetailIter iter); + + abstract void remove(DetailIter iter); + + private static class ItemStackEntry implements Map.Entry { + private final ItemStack key; + private final Map.Entry entry; + + public ItemStackEntry(ItemStack key, Map.Entry entry) { + this.key = key; + this.entry = entry; + } + + @Override + public ItemStack getKey() { + return key; + } + + @Override + public T getValue() { + return entry.getValue(); + } + + @Override + public T setValue(T value) { + return entry.setValue(value); + } + } + } + } + + private class DetailMap { + private boolean hasWildcard; + private T wildcard; + private HashMap damageMap; + private HashMap tagMap; + private HashMap metaMap; + private int size; + + public T get(ItemStack key) { + if (wildcard != null) + return wildcard; + + if (damageMap != null) { + final T ret = damageMap.get(actualDamage(key)); + if (ret != null) return ret; + } + if (tagMap != null) { + final T ret = tagMap.get(key.stackTagCompound); + if (ret != null) return ret; + } + if (metaMap != null) + return metaMap.get(new StackMetaKey(key)); + + return null; + } + + public T put(ItemStack key, T value) { + try { + switch (getKeyType(actualDamage(key), key.stackTagCompound)) { + case 0: + if (metaMap == null) metaMap = new HashMap<>(); + return metaMap.put(new StackMetaKey(key), value); + case 1: + if (tagMap == null) tagMap = new HashMap<>(); + return tagMap.put(key.stackTagCompound, value); + case 2: + if (damageMap == null) damageMap = new HashMap<>(); + return damageMap.put(actualDamage(key), value); + case 3: + T ret = wildcard; + wildcard = value; + hasWildcard = true; + return ret; + } + } finally { + updateSize(); + } + return null; + } + + public T remove(ItemStack key) { + try { + switch (getKeyType(actualDamage(key), key.stackTagCompound)) { + case 0: + return metaMap != null ? metaMap.remove(new StackMetaKey(key)) : null; + case 1: + return tagMap != null ? tagMap.remove(key.stackTagCompound) : null; + case 2: + return damageMap != null ? damageMap.remove(actualDamage(key)) : null; + case 3: + T ret = wildcard; + wildcard = null; + hasWildcard = false; + return ret; + } + } finally { + updateSize(); + } + return null; + } + + private void updateSize() { + int newSize = (hasWildcard ? 1 : 0) + + (metaMap != null ? metaMap.size() : 0) + + (tagMap != null ? tagMap.size() : 0) + + (damageMap != null ? damageMap.size() : 0); + + if (newSize != size) { + ItemStackMap.this.size += newSize - size; + size = newSize; + } + } + } + + private class SetView extends AbstractSet> { + @Override + public boolean contains(Object o) { + if (!(o instanceof Map.Entry)) return false; + Map.Entry entry = (Map.Entry) o; + return java.util.Objects.equals(get(entry.getKey()), entry.getValue()); + } + + @Override + public boolean add(Map.Entry itemStackTEntry) { + return itemStackTEntry.getKey() != null && itemStackTEntry.getValue() != null && + put(itemStackTEntry.getKey(), itemStackTEntry.getValue()) == null; + } + + @Override + public boolean remove(Object o) { + if (!(o instanceof Map.Entry)) return false; + Map.Entry entry = (Map.Entry) o; + return ItemStackMap.this.remove(entry.getKey(), entry.getValue()); + } + + @Nonnull + @Override + public Iterator> iterator() { + return Iterators.concat(Iterators.transform(itemMap.entrySet().iterator(), DetailIter::new)); + } + + @Override + public int size() { + return size; + } + } +}