/* * This file is part of Applied Energistics 2. * Copyright (c) 2021, TeamAppliedEnergistics, All rights reserved. * * Applied Energistics 2 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 3 of the License, or * (at your option) any later version. * * Applied Energistics 2 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 Applied Energistics 2. If not, see . */ package appeng.util.item; import appeng.api.config.FuzzyMode; import appeng.api.storage.data.IAEItemStack; import appeng.integration.modules.gregtech.ToolClass; import appeng.util.Platform; import com.google.common.base.Preconditions; import ic2.api.item.ICustomDamageItem; import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap; import net.minecraft.item.ItemStack; import java.util.Collection; import java.util.Comparator; import java.util.Map; /** * This variant list is optimized for damageable items, and supports selecting durability ranges with * {@link #findFuzzy(IAEItemStack, FuzzyMode)}. */ class FuzzyItemVariantList extends ItemVariantList { static final SharedStackComparator COMPARATOR = new SharedStackComparator(); // NOTE: We only use Object as they key here so we can pass our special DamageBounds to the subMap method. // We NEVER put any keys in this map that are not AESharedItemStacks. private final Object2ObjectSortedMap records = new Object2ObjectAVLTreeMap<>(COMPARATOR); @Override public Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) { ItemStack itemStack = filter.getDefinition(); ItemDamageBound lowerBound = makeLowerBound(itemStack, fuzzy); ItemDamageBound upperBound = makeUpperBound(itemStack, fuzzy); Preconditions.checkState(lowerBound.itemDamage > upperBound.itemDamage); return this.records.subMap(lowerBound, upperBound).values(); } @SuppressWarnings("unchecked") @Override Map getRecords() { // We ensure on our end that we NEVER use anything but AESharedItemStack as the key in this map return (Map) (Object) this.records; } static class ItemDamageBound { final int itemDamage; public ItemDamageBound(int itemDamage) { this.itemDamage = itemDamage; } } /** * This comparator creates a strict and total ordering over all {@link AESharedItemStack} of the same item. To * support selecting ranges of durability, it is defined for type {@link Object} and also accepts * {@link ItemDamageBound} as an argument to compare against. */ static class SharedStackComparator implements Comparator { @Override public int compare(Object a, Object b) { // Either argument can either be a damage bound or a shared item stack // Since we never put damage bounds into the map as keys, only one // of the two arguments can possibly be a bound ItemDamageBound boundA = null; AESharedItemStack stackA = null; int itemDamageA; if (a instanceof ItemDamageBound) { boundA = (ItemDamageBound) a; itemDamageA = boundA.itemDamage; } else { stackA = (AESharedItemStack) a; itemDamageA = stackA.getItemDamage(); } ItemDamageBound boundB = null; AESharedItemStack stackB = null; int itemDamageB; if (b instanceof ItemDamageBound) { boundB = (ItemDamageBound) b; itemDamageB = boundB.itemDamage; } else { stackB = (AESharedItemStack) b; itemDamageB = stackB.getItemDamage(); } // When either argument is a damage bound, we just compare the damage values because it is used // only to get a certain damage range out of the map. if (boundA != null || boundB != null) { return Integer.compare(itemDamageB, itemDamageA); } ItemStack itemStackA = stackA.getDefinition(); ItemStack itemStackB = stackB.getDefinition(); Preconditions.checkState(itemStackA.getCount() == 1, "ItemStack#getCount() has to be 1"); Preconditions.checkArgument(itemStackB.getCount() == 1, "ItemStack#getCount() has to be 1"); if (itemStackA == itemStackB) { return 0; } // Damaged items are sorted before undamaged items final int damageValue = Integer.compare(itemDamageB, itemDamageA); if (damageValue != 0) { return damageValue; } // As a final tie breaker, order by the object identity of the item stack // While this will order seemingly at random, we only need the order of // damage values to be predictable, while still having to satisfy the // complete order requirements of the sorted map return Long.compare(System.identityHashCode(itemStackA), System.identityHashCode(itemStackB)); } } /** * Minecraft reverses the damage values. So anything with a damage of 0 is undamaged and increases the more damaged * the item is. *

* Further the used subMap follows [MAX_DAMAGE, MIN_DAMAGE), so to include undamaged items, we have to start with a * lower damage value than 0, while it is fine to use {@link ItemStack#getMaxDamage()} for the upper bound. */ private static final int MIN_DAMAGE_VALUE = -1; /* * Keep in mind that the stack order is from most damaged to least damaged, so this lower bound will actually be a * higher number than the upper bound. */ static ItemDamageBound makeLowerBound(final ItemStack stack, final FuzzyMode fuzzy) { Preconditions.checkState(stack.getItem().isDamageable() || (Platform.isGTDamageableItem(stack.getItem())), "Item#isDamageable() has to be true"); int damage; int maxDamage; if (Platform.isIC2DamageableItem(stack.getItem())) { maxDamage = ((ICustomDamageItem) stack.getItem()).getMaxCustomDamage(stack); damage = ((ICustomDamageItem) stack.getItem()).getCustomDamage(stack); } else if (Platform.isGTDamageableItem(stack.getItem())) { maxDamage = ToolClass.getGTMaxDamage(stack); damage = ToolClass.getGTitemDamage(stack); } else { maxDamage = stack.getMaxDamage(); damage = stack.getItemDamage(); } if (fuzzy == FuzzyMode.IGNORE_ALL) { if (maxDamage != 0) { damage = maxDamage; } } else { final int breakpoint = fuzzy.calculateBreakPoint(maxDamage); damage = damage <= breakpoint ? breakpoint : maxDamage; } return new ItemDamageBound(damage); } /* * Keep in mind that the stack order is from most damaged to least damaged, so this upper bound will actually be a * lower number than the lower bound. It also is exclusive. */ static ItemDamageBound makeUpperBound(final ItemStack stack, final FuzzyMode fuzzy) { Preconditions.checkState(stack.getItem().isDamageable() || (Platform.isGTDamageableItem(stack.getItem())), "Item#isDamageable() has to be true"); int damage; if (fuzzy == FuzzyMode.IGNORE_ALL) { damage = MIN_DAMAGE_VALUE; } else { int maxDamage; if (Platform.isIC2DamageableItem(stack.getItem())) { maxDamage = ((ICustomDamageItem) stack.getItem()).getMaxCustomDamage(stack); damage = ((ICustomDamageItem) stack.getItem()).getCustomDamage(stack); } else if (Platform.isGTDamageableItem(stack.getItem())) { maxDamage = ToolClass.getGTMaxDamage(stack); damage = ToolClass.getGTitemDamage(stack); } else { maxDamage = stack.getMaxDamage(); damage = stack.getItemDamage(); } final int breakpoint = fuzzy.calculateBreakPoint(maxDamage); damage = damage <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint; } return new ItemDamageBound(damage); } }