From 036f4e1c5bcc79921d18480343cb976c2735ac17 Mon Sep 17 00:00:00 2001 From: PrototypeTrousers Date: Wed, 9 Mar 2022 23:08:47 -0300 Subject: [PATCH] heavily tweaked auto-crafting --- gradle.properties | 3 +- gradle/scripts/dependencies.gradle | 2 + .../java/appeng/crafting/CraftingJob.java | 27 +- .../appeng/crafting/CraftingTreeNode.java | 65 ++-- .../appeng/crafting/CraftingTreeProcess.java | 63 ++-- .../appeng/crafting/MECraftingInventory.java | 2 +- .../java/appeng/helpers/PatternHelper.java | 86 +++-- .../appeng/parts/misc/ItemHandlerAdapter.java | 70 ++-- .../java/appeng/util/item/AEItemStack.java | 35 +- .../util/item/FuzzyItemVariantList.java | 288 +++++++++------- src/main/java/appeng/util/item/ItemList.java | 317 ++++++++++-------- 11 files changed, 571 insertions(+), 387 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2ad6bc322..0dd0cb91d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ aebuild=7 aegroup=appeng aebasename=appliedenergistics2 extended=extended_life -extendedversion=v50l +extendedversion=v51 ######################################################### # Versions # ######################################################### @@ -26,6 +26,7 @@ cofhcore_version=1.12.2-4.5.2.19 crafttweaker_version=4.1.8.9 inventorytweaks_version=1.63 ctm_version=MC1.12.2-0.3.1.16 +forestry_version=5.8.2.387 ######################################################### # Deployment # ######################################################### diff --git a/gradle/scripts/dependencies.gradle b/gradle/scripts/dependencies.gradle index 6f43161fa..ae2efaf2e 100644 --- a/gradle/scripts/dependencies.gradle +++ b/gradle/scripts/dependencies.gradle @@ -97,6 +97,8 @@ dependencies { compileOnly "inventory-tweaks:InventoryTweaks:${inventorytweaks_version}:api" compileOnly "team.chisel.ctm:CTM:${ctm_version}" compileOnly "de.ellpeck.actuallyadditions:ActuallyAdditions:1.12.2-r152.16:api" + deobfCompile "net.sengir.forestry:forestry_${minecraft_version}:$forestry_version" + // at runtime, use the full JEI jar runtime "mezz.jei:jei_${minecraft_version}:${jei_version}" diff --git a/src/main/java/appeng/crafting/CraftingJob.java b/src/main/java/appeng/crafting/CraftingJob.java index c934774e0..9e43cd323 100644 --- a/src/main/java/appeng/crafting/CraftingJob.java +++ b/src/main/java/appeng/crafting/CraftingJob.java @@ -65,6 +65,7 @@ public class CraftingJob implements Runnable, ICraftingJob private final Object monitor = new Object(); private final Stopwatch tickSpreadingWatch = Stopwatch.createUnstarted(); private final Stopwatch craftingTreeWatch = Stopwatch.createUnstarted(); + private final ICraftingGrid cc; private CraftingTreeNode tree; private final IAEItemStack output; private boolean simulate = false; @@ -90,7 +91,7 @@ public class CraftingJob implements Runnable, ICraftingJob this.callback = callback; - final ICraftingGrid cc = grid.getCache( ICraftingGrid.class ); + this.cc = grid.getCache( ICraftingGrid.class ); final GridStorageCache sg = grid.getCache( IStorageGrid.class ); this.original = new MECraftingInventory( sg.getExtractableList( actionSrc ) ); @@ -159,13 +160,13 @@ public class CraftingJob implements Runnable, ICraftingJob TickHandler.INSTANCE.registerCraftingSimulation( this.world, this ); this.handlePausing(); - craftingTreeWatch.start(); - final MECraftingInventory craftingInventory = new MECraftingInventory( this.original, true, false, true ); craftingInventory.ignore( this.output ); this.availableCheck = new MECraftingInventory( this.original, false, false, false ); + craftingTreeWatch.start(); this.getTree().request( craftingInventory, this.output.getStackSize(), this.actionSrc ); + craftingTreeWatch.stop(); this.getTree().dive( this ); for( final String s : this.opsAndMultiplier.keySet() ) @@ -174,8 +175,14 @@ public class CraftingJob implements Runnable, ICraftingJob AELog.crafting( s + " * " + ti.times + " = " + ( ti.perOp * ti.times ) ); } - craftingTreeWatch.stop(); - this.logCraftingJob( "real, success", craftingTreeWatch ); + if( actionSrc.player().isPresent() ) + { + this.logCraftingJob( "simulated, success", craftingTreeWatch ); + } + else + { + this.logCraftingJob( "real, success", craftingTreeWatch ); + } } catch( final CraftBranchFailure e ) { @@ -185,14 +192,14 @@ public class CraftingJob implements Runnable, ICraftingJob { if( actionSrc.player().isPresent() ) { - craftingTreeWatch.reset().start(); final MECraftingInventory craftingInventory = new MECraftingInventory( this.original, true, false, true ); craftingInventory.ignore( this.output ); - this.availableCheck = new MECraftingInventory( this.original, false, false, false ); - this.getTree().setSimulate(); + this.availableCheck = new MECraftingInventory( this.original, false, false, false ); + craftingTreeWatch.reset().start(); this.getTree().request( craftingInventory, this.output.getStackSize(), this.actionSrc ); + craftingTreeWatch.stop(); this.getTree().dive( this ); for( final String s : this.opsAndMultiplier.keySet() ) @@ -201,12 +208,10 @@ public class CraftingJob implements Runnable, ICraftingJob AELog.crafting( s + " * " + ti.times + " = " + ( ti.perOp * ti.times ) ); } - craftingTreeWatch.stop(); - this.logCraftingJob( "simulate", craftingTreeWatch ); + this.logCraftingJob( "simulated, failed", craftingTreeWatch ); } else { - craftingTreeWatch.stop(); this.logCraftingJob( "real, failed", craftingTreeWatch ); } } diff --git a/src/main/java/appeng/crafting/CraftingTreeNode.java b/src/main/java/appeng/crafting/CraftingTreeNode.java index 4cbc0433f..0ab5942a4 100644 --- a/src/main/java/appeng/crafting/CraftingTreeNode.java +++ b/src/main/java/appeng/crafting/CraftingTreeNode.java @@ -24,7 +24,6 @@ import java.util.Collection; import java.util.List; import appeng.api.config.FuzzyMode; -import com.google.common.collect.Lists; import net.minecraft.util.text.TextComponentString; import net.minecraft.world.World; @@ -53,14 +52,14 @@ public class CraftingTreeNode private final IAEItemStack what; // what are the crafting patterns for this? private final ArrayList nodes = new ArrayList<>(); + private final ICraftingGrid cc; + private final int depth; private int bytes = 0; private boolean canEmit = false; private long missing = 0; private long howManyEmitted = 0; private boolean exhausted = false; - private boolean sim; - public CraftingTreeNode( final ICraftingGrid cc, final CraftingJob job, final IAEItemStack wat, final CraftingTreeProcess par, final int slot, final int depth ) { this.what = wat; @@ -68,9 +67,18 @@ public class CraftingTreeNode this.slot = slot; this.world = job.getWorld(); this.job = job; - this.sim = false; + this.cc = cc; + this.depth = depth; this.canEmit = cc.canEmitFor( this.what ); + } + + public void addNode() + { + if( !nodes.isEmpty() ) + { + return; + } if( this.canEmit ) { @@ -89,19 +97,42 @@ public class CraftingTreeNode IAEItemStack request( final MECraftingInventory inv, long l, final IActionSource src ) throws CraftBranchFailure, InterruptedException { + addNode(); this.job.handlePausing(); + if( this.canEmit ) + { + final IAEItemStack wat = this.what.copy(); + wat.setStackSize( l ); + + this.howManyEmitted = wat.getStackSize(); + this.bytes += wat.getStackSize(); + + return wat; + } final IItemList inventoryList = inv.getItemList(); final List thingsUsed = new ArrayList<>(); this.what.setStackSize( l ); + if( this.getSlot() >= 0 && this.parent != null && this.parent.details.isCraftable() ) { Collection itemList = new ArrayList<>(); - if( this.parent.getContainerItems() != null && !this.parent.getContainerItems().findFuzzy( this.what, FuzzyMode.IGNORE_ALL ).isEmpty() ) + if( this.what.getItem().hasContainerItem( this.what.getDefinition() ) ) { - itemList = inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL ); + itemList.addAll( inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL ) ); + + if( this.parent.details.canSubstitute() ) + { + for( IAEItemStack is : inventoryList ) + { + if( is.fuzzyComparison( this.what, FuzzyMode.IGNORE_ALL ) ) + { + itemList.add( is ); + } + } + } } else { @@ -110,6 +141,10 @@ public class CraftingTreeNode { itemList.add( item ); } + if( this.parent.details.canSubstitute() ) + { + itemList.addAll( inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL ) ); + } } for( IAEItemStack fuzz : itemList ) @@ -172,17 +207,6 @@ public class CraftingTreeNode } } - if( this.canEmit ) - { - final IAEItemStack wat = this.what.copy(); - wat.setStackSize( l ); - - this.howManyEmitted = wat.getStackSize(); - this.bytes += wat.getStackSize(); - - return wat; - } - this.exhausted = true; if( this.nodes.size() == 1 ) @@ -192,11 +216,9 @@ public class CraftingTreeNode while ( pro.possible && l > 0 ) { final IAEItemStack madeWhat = pro.getAmountCrafted( this.what ); - pro.request( inv, pro.getTimes( l, madeWhat.getStackSize() ), src ); madeWhat.setStackSize( l ); - final IAEItemStack available = inv.extractItems( madeWhat, Actionable.MODULATE, src ); if( available != null ) @@ -257,10 +279,10 @@ public class CraftingTreeNode } } - if( this.sim ) + if( job.isSimulation() ) { - this.missing += l; this.bytes += l; + this.missing += l; final IAEItemStack rv = this.what.copy(); rv.setStackSize( l ); return rv; @@ -306,7 +328,6 @@ public class CraftingTreeNode void setSimulate() { - this.sim = true; this.missing = 0; this.bytes = 0; this.used.resetStatus(); diff --git a/src/main/java/appeng/crafting/CraftingTreeProcess.java b/src/main/java/appeng/crafting/CraftingTreeProcess.java index b85cc45b2..eb28a7f25 100644 --- a/src/main/java/appeng/crafting/CraftingTreeProcess.java +++ b/src/main/java/appeng/crafting/CraftingTreeProcess.java @@ -20,15 +20,13 @@ package appeng.crafting; import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map.Entry; import appeng.api.config.FuzzyMode; +import appeng.me.cache.CraftingGridCache; import appeng.util.item.AEItemStack; import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.Lists; import it.unimi.dsi.fastutil.objects.Object2LongArrayMap; import net.minecraft.item.ItemStack; import net.minecraft.world.World; @@ -52,11 +50,11 @@ public class CraftingTreeProcess private final CraftingJob job; private final Object2LongArrayMap nodes = new Object2LongArrayMap<>(); private final int depth; + private final ICraftingGrid cc; + private final World world; boolean possible = true; private long crafts = 0; - private IItemList containerItems; private boolean limitQty; - private List containerItemsList; private long bytes = 0; public CraftingTreeProcess( final ICraftingGrid cc, final CraftingJob job, final ICraftingPatternDetails details, final CraftingTreeNode craftingTreeNode, final int depth ) @@ -65,20 +63,22 @@ public class CraftingTreeProcess this.details = details; this.job = job; this.depth = depth; - final World world = job.getWorld(); + this.cc = cc; + this.world = job.getWorld(); + } + public void addProcess() + { + if( !nodes.isEmpty() ) + { + return; + } final IAEItemStack[] list = details.getInputs(); for( final IAEItemStack part : details.getCondensedInputs() ) { if( part.getItem().hasContainerItem( part.getDefinition() ) ) { - if( containerItems == null ) - { - containerItems = AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList(); - containerItemsList = new ArrayList<>(); - } - containerItems.add( part ); this.limitQty = true; //break; } @@ -96,13 +96,33 @@ public class CraftingTreeProcess if( part.equals( comparePart ) ) { boolean isPartContainer = false; - if( containerItems != null && !containerItems.findFuzzy( list[x], FuzzyMode.IGNORE_ALL ).isEmpty() ) + if( part.getItem().hasContainerItem( part.getDefinition() ) ) { part = list[x]; isPartContainer = true; } long wantedSize = part.getStackSize(); + if( isPartContainer && wantedSize > 0 ) + { + if( details.canSubstitute() && cc.getCraftingFor( part, details, x, world ).isEmpty() ) + { + IItemList aa = ( (CraftingGridCache) cc ).getAvailableItems( AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList() ); + for( IAEItemStack is : aa ) + { + if( is.fuzzyComparison( part, FuzzyMode.IGNORE_ALL ) ) + { + wantedSize -= 1; + this.nodes.put( new CraftingTreeNode( cc, job, is.copy().setStackSize( 1 ), this, x, depth + 1 ), 1 ); + if( wantedSize == 0 ) + { + break; + } + } + } + } + } + if( !isPartContainer ) { IAEItemStack found = job.checkAvailable( part ); @@ -270,11 +290,6 @@ public class CraftingTreeProcess } } - IItemList getContainerItems() - { - return this.containerItems; - } - boolean notRecursive() { return this.parent == null || this.parent.notRecursive(); @@ -291,28 +306,34 @@ public class CraftingTreeProcess void request( final MECraftingInventory inv, final long amountOfTimes, final IActionSource src ) throws CraftBranchFailure, InterruptedException { + addProcess(); this.job.handlePausing(); + List containerItems = null; // request and remove inputs... for( final Entry entry : this.nodes.object2LongEntrySet() ) { final IAEItemStack stack = entry.getKey().request( inv, entry.getValue() * amountOfTimes, src ); - if( containerItems != null && !this.containerItems.findFuzzy( stack, FuzzyMode.IGNORE_ALL ).isEmpty() ) + if( stack.getItem().hasContainerItem( stack.getDefinition() ) ) { final ItemStack is = Platform.getContainerItem( stack.createItemStack() ); final IAEItemStack o = AEItemStack.fromItemStack( is ); if( o != null ) { + if( containerItems == null ) + { + containerItems = new ArrayList<>(); + } this.bytes++; - containerItemsList.add( o ); + containerItems.add( o ); } } } if( containerItems != null ) { - for( IAEItemStack i : containerItemsList ) + for( IAEItemStack i : containerItems ) { inv.injectItems( i, Actionable.MODULATE, src ); } diff --git a/src/main/java/appeng/crafting/MECraftingInventory.java b/src/main/java/appeng/crafting/MECraftingInventory.java index 379937498..b83b0c446 100644 --- a/src/main/java/appeng/crafting/MECraftingInventory.java +++ b/src/main/java/appeng/crafting/MECraftingInventory.java @@ -138,7 +138,7 @@ public class MECraftingInventory implements IMEInventory this.localCache = new ItemListIgnoreCrafting<>( AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList() ); for( final IAEItemStack is : target.getStorageList() ) { - this.localCache.add( target.extractItems( is, Actionable.SIMULATE, src ) ); + this.localCache.add( target.extractItems( is.copy().setStackSize( Long.MAX_VALUE ), Actionable.SIMULATE, src ) ); } this.par = null; diff --git a/src/main/java/appeng/helpers/PatternHelper.java b/src/main/java/appeng/helpers/PatternHelper.java index c8eb73405..2f830e185 100644 --- a/src/main/java/appeng/helpers/PatternHelper.java +++ b/src/main/java/appeng/helpers/PatternHelper.java @@ -21,6 +21,7 @@ package appeng.helpers; import java.util.*; +import gregtech.common.items.MetaTool; import net.minecraft.inventory.InventoryCrafting; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -247,7 +248,7 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable getSubstituteInputs(int slot) { - if (this.inputs[slot] == null) { + public List getSubstituteInputs( int slot ) + { + if( this.inputs[slot] == null ) + { return Collections.emptyList(); } - return this.substituteInputs.computeIfAbsent(slot, value -> { - ItemStack[] matchingStacks = getRecipeIngredient(slot).getMatchingStacks(); - List itemList = new ArrayList<>(matchingStacks.length + 1); - for (ItemStack matchingStack : matchingStacks) { - itemList.add(AEItemStack.fromItemStack(matchingStack)); + return this.substituteInputs.computeIfAbsent( slot, value -> { + ItemStack[] matchingStacks = getRecipeIngredient( slot ).getMatchingStacks(); + List itemList = new ArrayList<>( matchingStacks.length + 1 ); + for( ItemStack matchingStack : matchingStacks ) + { + itemList.add( AEItemStack.fromItemStack( matchingStack ) ); } // Ensure that the specific item put in by the user is at the beginning, // so that it takes precedence over substitutions - itemList.add(0, this.inputs[slot]); + itemList.add( 0, this.inputs[slot] ); return itemList; - }); + } ); } /** @@ -352,31 +358,40 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable0 if they have empty rows above or to the left of their input int topOffset = 0; - if (inputs[0] == null && inputs[1] == null && inputs[2] == null) { + if( inputs[0] == null && inputs[1] == null && inputs[2] == null ) + { topOffset++; // First row is fully empty - if (inputs[3] == null && inputs[4] == null && inputs[5] == null) { + if( inputs[3] == null && inputs[4] == null && inputs[5] == null ) + { topOffset++; // Second row is fully empty } } int leftOffset = 0; - if (inputs[0] == null && inputs[3] == null && inputs[6] == null) { + if( inputs[0] == null && inputs[3] == null && inputs[6] == null ) + { leftOffset++; // First column is fully empty - if (inputs[1] == null && inputs[4] == null && inputs[7] == null) { + if( inputs[1] == null && inputs[4] == null && inputs[7] == null ) + { leftOffset++; // Second column is fully empty } } @@ -390,32 +405,37 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable ingredients = standardRecipe.getIngredients(); - if (ingredientIndex < 0 || ingredientIndex > ingredients.size()) { + if( ingredientIndex < 0 || ingredientIndex > ingredients.size() ) + { return Ingredient.EMPTY; } - return ingredients.get(ingredientIndex); + return ingredients.get( ingredientIndex ); } - private Ingredient getShapelessRecipeIngredient(int slot) { + private Ingredient getShapelessRecipeIngredient( int slot ) + { // We map the list of *filled* sparse inputs to the shapeless (ergo unordered) // ingredients. While these do not actually correspond to each other, // since both lists have the same length, the mapping is at least stable. int ingredientIndex = 0; - for (int i = 0; i < slot; i++) { - if (inputs[i] != null) { + for( int i = 0; i < slot; i++ ) + { + if( inputs[i] != null ) + { ingredientIndex++; } } NonNullList ingredients = standardRecipe.getIngredients(); - if (ingredientIndex < ingredients.size()) { - return ingredients.get(ingredientIndex); + if( ingredientIndex < ingredients.size() ) + { + return ingredients.get( ingredientIndex ); } return Ingredient.EMPTY; } - + @Override public ItemStack getOutput( final InventoryCrafting craftingInv, final World w ) { @@ -517,7 +537,9 @@ public class PatternHelper implements ICraftingPatternDetails, Comparable, IBaseMonitor - // maxSize, even if we request more. So even if it returns a valid stack, it might have more stuff. - do + if( !simulate ) { - extracted = this.itemHandler.extractItem( i, remainingCurrentSlot, simulate ); + int stackSizeCurrentSlot = stackInInventorySlot.getCount(); + int remainingCurrentSlot = Math.min( remainingSize, stackSizeCurrentSlot ); + + // We have to loop here because according to the docs, the handler shouldn't return a stack with size > + // maxSize, even if we request more. So even if it returns a valid stack, it might have more stuff. + + do + { + extracted = this.itemHandler.extractItem( i, remainingCurrentSlot, false ); + + if( !extracted.isEmpty() ) + { + if( extracted.getCount() > remainingCurrentSlot ) + { + // Something broke. It should never return more than we requested... + // We're going to silently eat the remainder + AELog.warn( "Mod that provided item handler %s is broken. Returned %s items while only requesting %d.", this.itemHandler.getClass().getName(), extracted.toString(), remainingCurrentSlot ); + extracted.setCount( remainingCurrentSlot ); + } + + // We're just gonna use the first stack we get our hands on as the template for the rest. + // In case some stupid itemhandler (aka forge) returns an internal state we have to do a second + // expensive copy again. + if( gathered.isEmpty() ) + { + gathered = extracted; + } + else + { + gathered.grow( extracted.getCount() ); + } + remainingCurrentSlot -= extracted.getCount(); + } + } while ( !extracted.isEmpty() && remainingCurrentSlot > 0 ); + + remainingSize -= stackSizeCurrentSlot - remainingCurrentSlot; + } + else + { + extracted = this.itemHandler.extractItem( i, remainingSize, true ); if( !extracted.isEmpty() ) { - if( extracted.getCount() > remainingCurrentSlot ) - { - // Something broke. It should never return more than we requested... - // We're going to silently eat the remainder - AELog.warn( "Mod that provided item handler %s is broken. Returned %s items while only requesting %d.", this.itemHandler.getClass().getName(), extracted.toString(), remainingCurrentSlot ); - extracted.setCount( remainingCurrentSlot ); - } - - // We're just gonna use the first stack we get our hands on as the template for the rest. - // In case some stupid itemhandler (aka forge) returns an internal state we have to do a second - // expensive copy again. + extracted.setCount( Math.min( stackInInventorySlot.getCount(), remainingSize ) ); if( gathered.isEmpty() ) { - gathered = extracted.copy(); + gathered = extracted; } else { gathered.grow( extracted.getCount() ); } - remainingCurrentSlot -= extracted.getCount(); + remainingSize -= extracted.getCount(); } - } while ( !extracted.isEmpty() && remainingCurrentSlot > 0 ); - - remainingSize -= stackSizeCurrentSlot - remainingCurrentSlot; - - // Done? + } if( remainingSize <= 0 ) { break; diff --git a/src/main/java/appeng/util/item/AEItemStack.java b/src/main/java/appeng/util/item/AEItemStack.java index d18d95785..22cbf20e7 100644 --- a/src/main/java/appeng/util/item/AEItemStack.java +++ b/src/main/java/appeng/util/item/AEItemStack.java @@ -27,6 +27,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import com.google.common.primitives.Ints; +import gregtech.common.items.MetaTool; import io.netty.buffer.ByteBuf; import net.minecraft.item.Item; import net.minecraft.item.ItemStack; @@ -370,20 +371,44 @@ public final class AEItemStack extends AEStack implements IAEItemS private boolean fuzzyItemStackComparison( ItemStack a, ItemStack b, FuzzyMode mode ) { - if( a.getItem() == b.getItem() && a.getItem().isDamageable() ) + if( a.getItem() == b.getItem() && ( a.getItem().isDamageable() || a.getItem() instanceof MetaTool ) ) { if( mode == FuzzyMode.IGNORE_ALL ) { - return true; + if( a.getItem().isDamageable() ) + { + return true; + } + else if( a.getItem() instanceof MetaTool ) + { + return a.getItemDamage() == b.getItemDamage(); + } } else if( mode == FuzzyMode.PERCENT_99 ) { - return a.getItemDamage() > 1 == b.getItemDamage() > 1; + if( a.getItem().isDamageable() ) + { + return a.getItemDamage() > 1 == b.getItemDamage() > 1; + } + else if( a.getItem() instanceof MetaTool ) + { + return ( (MetaTool) a.getItem() ).getItemDamage( a ) == ( (MetaTool) b.getItem() ).getItemDamage( b ); + } } else { - final float percentDamageOfA = (float) a.getItemDamage() / a.getMaxDamage(); - final float percentDamageOfB = (float) b.getItemDamage() / b.getMaxDamage(); + float percentDamageOfA = 0; + float percentDamageOfB = 0; + if( a.getItem().isDamageable() ) + { + percentDamageOfA = (float) a.getItemDamage() / a.getMaxDamage(); + percentDamageOfB = (float) b.getItemDamage() / b.getMaxDamage(); + } + else if( a.getItem() instanceof MetaTool ) + { + percentDamageOfA = (float) ( (MetaTool) a.getItem() ).getItemDamage( a ) / ( (MetaTool) a.getItem() ).getMaxItemDamage( a ); + percentDamageOfB = (float) ( (MetaTool) b.getItem() ).getItemDamage( b ) / ( (MetaTool) b.getItem() ).getMaxItemDamage( b ); + } return percentDamageOfA > mode.breakPoint == percentDamageOfB > mode.breakPoint; } diff --git a/src/main/java/appeng/util/item/FuzzyItemVariantList.java b/src/main/java/appeng/util/item/FuzzyItemVariantList.java index 4968cd6f8..38ace9466 100644 --- a/src/main/java/appeng/util/item/FuzzyItemVariantList.java +++ b/src/main/java/appeng/util/item/FuzzyItemVariantList.java @@ -25,7 +25,9 @@ import java.util.Map; import appeng.util.Platform; import com.google.common.base.Preconditions; +import gregtech.api.items.toolitem.ToolMetaItem; import gregtech.common.items.MetaTool; +import it.unimi.dsi.fastutil.objects.ObjectCollection; import net.minecraft.item.ItemStack; import it.unimi.dsi.fastutil.objects.Object2ObjectAVLTreeMap; @@ -34,159 +36,187 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap; import appeng.api.config.FuzzyMode; import appeng.api.storage.data.IAEItemStack; + /** * This variant list is optimized for damageable items, and supports selecting durability ranges with * {@link #findFuzzy(IAEItemStack, FuzzyMode)}. */ -class FuzzyItemVariantList extends ItemVariantList { +class FuzzyItemVariantList extends ItemVariantList +{ - static final SharedStackComparator COMPARATOR = new SharedStackComparator(); + 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); + // 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(); + @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); + ItemDamageBound lowerBound = makeLowerBound( itemStack, fuzzy ); + ItemDamageBound upperBound = makeUpperBound( itemStack, fuzzy ); + Preconditions.checkState( lowerBound.itemDamage > upperBound.itemDamage ); - return this.records.subMap(lowerBound, upperBound).values(); - } + 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; - } + @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; + static class ItemDamageBound + { + final int itemDamage; - public ItemDamageBound(int itemDamage) { - this.itemDamage = 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(); - } + /** + * 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); - } + // 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"); + 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; - } + if( itemStackA == itemStackB ) + { + return 0; + } - // Damaged items are sorted before undamaged items - final int damageValue = Integer.compare(itemDamageB, itemDamageA); - if (damageValue != 0) { - return damageValue; - } + // 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)); - } - } + // 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; + /** + * 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() , "Item#isDamageable() has to be true" ); + /* + * 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() || stack.getItem() instanceof MetaTool, "Item#isDamageable() has to be true" ); - int damage; - if( fuzzy == FuzzyMode.IGNORE_ALL ) - { - if( stack.getMaxDamage() == 0 ) - { - damage = stack.getItemDamage(); - } - else - { - damage = stack.getMaxDamage(); - } - } - else - { - final int breakpoint = fuzzy.calculateBreakPoint( stack.getMaxDamage() ); - damage = stack.getItemDamage() <= breakpoint ? breakpoint : stack.getMaxDamage(); - } + int damage; + if( fuzzy == FuzzyMode.IGNORE_ALL ) + { + int maxDamage; + if( stack.getItem() instanceof MetaTool ) + { + maxDamage = ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack ); + damage = ( (MetaTool) stack.getItem() ).getItemDamage( stack ); + } + else + { + maxDamage = stack.getMaxDamage(); + damage = stack.getItemDamage(); + } + if( maxDamage != 0 ) + { + damage = maxDamage; + } + } + else + { + final int breakpoint = fuzzy.calculateBreakPoint( stack.getItem().isDamageable() ? stack.getMaxDamage() : ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack ) ); + damage = stack.getItem().isDamageable() ? stack.getItemDamage() : ( (MetaTool) stack.getItem() ).getItemDamage( stack ) <= breakpoint ? breakpoint : stack.getItem().isDamageable() ? stack.getMaxDamage() : ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack ); + } - return new ItemDamageBound( damage ); - } + 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() , "Item#isDamageable() has to be true"); + /* + * 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() || stack.getItem() instanceof MetaTool, "Item#isDamageable() has to be true" ); - int damage; - if (fuzzy == FuzzyMode.IGNORE_ALL) { - damage = MIN_DAMAGE_VALUE; - } else { - final int breakpoint = fuzzy.calculateBreakPoint(stack.getMaxDamage()); - damage = stack.getItemDamage() <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint; - } + int damage; + if( fuzzy == FuzzyMode.IGNORE_ALL ) + { + damage = MIN_DAMAGE_VALUE; + } + else + { + final int breakpoint = fuzzy.calculateBreakPoint( stack.getItem().isDamageable() ? stack.getMaxDamage() : ( (MetaTool) stack.getItem() ).getMaxItemDamage( stack ) ); + damage = stack.getItem().isDamageable() ? stack.getItemDamage() : ( (MetaTool) stack.getItem() ).getItemDamage( stack ) <= breakpoint ? MIN_DAMAGE_VALUE : breakpoint; + } - return new ItemDamageBound(damage); - } + return new ItemDamageBound( damage ); + } } diff --git a/src/main/java/appeng/util/item/ItemList.java b/src/main/java/appeng/util/item/ItemList.java index 069bdcc38..ca1bb6da8 100644 --- a/src/main/java/appeng/util/item/ItemList.java +++ b/src/main/java/appeng/util/item/ItemList.java @@ -36,180 +36,217 @@ import appeng.api.config.FuzzyMode; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; -public final class ItemList implements IItemList { - private final Reference2ObjectMap records = new Reference2ObjectOpenHashMap<>(); - /** - * We increment this version field everytime an attempt to mutate this item list (or potentially one of its - * sub-lists) is made. Iterators will copy the version when they are created and compare it against the current - * version whenever they advance to trigger a {@link ConcurrentModificationException}. - */ - private final AtomicInteger version = new AtomicInteger(0); +public final class ItemList implements IItemList +{ - @Override - public IAEItemStack findPrecise(final IAEItemStack itemStack) { - if (itemStack == null) { - return null; - } + private final Reference2ObjectMap records = new Reference2ObjectOpenHashMap<>(); + /** + * We increment this version field everytime an attempt to mutate this item list (or potentially one of its + * sub-lists) is made. Iterators will copy the version when they are created and compare it against the current + * version whenever they advance to trigger a {@link ConcurrentModificationException}. + */ + private final AtomicInteger version = new AtomicInteger( 0 ); - ItemVariantList record = this.records.get(itemStack.getItem()); - return record != null ? record.findPrecise(itemStack) : null; - } + @Override + public IAEItemStack findPrecise( final IAEItemStack itemStack ) + { + if( itemStack == null ) + { + return null; + } - @Override - public Collection findFuzzy(final IAEItemStack filter, final FuzzyMode fuzzy) { - if (filter == null) { - return Collections.emptyList(); - } + ItemVariantList record = this.records.get( itemStack.getItem() ); + return record != null ? record.findPrecise( itemStack ) : null; + } - ItemVariantList record = this.records.get(filter.getItem()); - return record != null ? record.findFuzzy(filter, fuzzy) : Collections.emptyList(); - } + @Override + public Collection findFuzzy( final IAEItemStack filter, final FuzzyMode fuzzy ) + { + if( filter == null ) + { + return Collections.emptyList(); + } - @Override - public boolean isEmpty() { - return !this.iterator().hasNext(); - } + ItemVariantList record = this.records.get( filter.getItem() ); + return record != null ? record.findFuzzy( filter, fuzzy ) : Collections.emptyList(); + } - @Override - public void add(final IAEItemStack itemStack) { - version.incrementAndGet(); + @Override + public boolean isEmpty() + { + return !this.iterator().hasNext(); + } - if (itemStack == null) { - return; - } + @Override + public void add( final IAEItemStack itemStack ) + { + version.incrementAndGet(); - this.getOrCreateRecord(itemStack.getItem()).add(itemStack); - } + if( itemStack == null ) + { + return; + } - @Override - public void addStorage(final IAEItemStack itemStack) { - version.incrementAndGet(); + this.getOrCreateRecord( itemStack.getItem() ).add( itemStack ); + } - if (itemStack == null) { - return; - } + @Override + public void addStorage( final IAEItemStack itemStack ) + { + version.incrementAndGet(); - this.getOrCreateRecord(itemStack.getItem()).addStorage(itemStack); - } + if( itemStack == null ) + { + return; + } - @Override - public void addCrafting(final IAEItemStack itemStack) { - version.incrementAndGet(); + this.getOrCreateRecord( itemStack.getItem() ).addStorage( itemStack ); + } - if (itemStack == null) { - return; - } + @Override + public void addCrafting( final IAEItemStack itemStack ) + { + version.incrementAndGet(); - this.getOrCreateRecord(itemStack.getItem()).addCrafting(itemStack); - } + if( itemStack == null ) + { + return; + } - @Override - public void addRequestable(final IAEItemStack itemStack) { - version.incrementAndGet(); + this.getOrCreateRecord( itemStack.getItem() ).addCrafting( itemStack ); + } - if (itemStack == null) { - return; - } + @Override + public void addRequestable( final IAEItemStack itemStack ) + { + version.incrementAndGet(); - this.getOrCreateRecord(itemStack.getItem()).addRequestable(itemStack); - } + if( itemStack == null ) + { + return; + } - @Override - public IAEItemStack getFirstItem() { - for (final IAEItemStack stackType : this) { - return stackType; - } + this.getOrCreateRecord( itemStack.getItem() ).addRequestable( itemStack ); + } - return null; - } + @Override + public IAEItemStack getFirstItem() + { + for( final IAEItemStack stackType : this ) + { + return stackType; + } - @Override - public int size() { - int size = 0; - for (ItemVariantList entry : records.values()) { - size += entry.size(); - } + return null; + } - return size; - } + @Override + public int size() + { + int size = 0; + for( ItemVariantList entry : records.values() ) + { + size += entry.size(); + } - @Override - public Iterator iterator() { - return new ChainedIterator(this.records.values().iterator(), version); - } + return size; + } - @Override - public void resetStatus() { - for (final IAEItemStack i : this) { - i.reset(); - } - } + @Override + public Iterator iterator() + { + return new ChainedIterator( this.records.values().iterator(), version ); + } - private ItemVariantList getOrCreateRecord(Item item) { - return this.records.computeIfAbsent(item, this::makeRecordMap); - } + @Override + public void resetStatus() + { + for( final IAEItemStack i : this ) + { + i.reset(); + } + } - private ItemVariantList makeRecordMap(Item item) { - if (item.isDamageable() ) { - return new FuzzyItemVariantList(); - } else { - return new NormalItemVariantList(); - } - } + private ItemVariantList getOrCreateRecord( Item item ) + { + return this.records.computeIfAbsent( item, this::makeRecordMap ); + } - /** - * Iterates over multiple item lists as if they were one list. - */ - private static class ChainedIterator implements Iterator { + private ItemVariantList makeRecordMap( Item item ) + { + if( item.isDamageable() || item instanceof MetaTool ) + { + return new FuzzyItemVariantList(); + } + else + { + return new NormalItemVariantList(); + } + } - private final AtomicInteger parentVersion; - private final int version; - private final Iterator parent; - private Iterator next; + /** + * Iterates over multiple item lists as if they were one list. + */ + private static class ChainedIterator implements Iterator + { - public ChainedIterator(Iterator iterator, AtomicInteger parentVersion) { - this.parent = iterator; - this.parentVersion = parentVersion; - this.version = parentVersion.get(); - this.ensureItems(); - } + private final AtomicInteger parentVersion; + private final int version; + private final Iterator parent; + private Iterator next; - @Override - public boolean hasNext() { - return next != null && next.hasNext(); - } + public ChainedIterator( Iterator iterator, AtomicInteger parentVersion ) + { + this.parent = iterator; + this.parentVersion = parentVersion; + this.version = parentVersion.get(); + this.ensureItems(); + } - @Override - public IAEItemStack next() { - if (this.next == null) { - throw new NoSuchElementException(); - } - if (this.version != this.parentVersion.get()) { - throw new ConcurrentModificationException(); - } + @Override + public boolean hasNext() + { + return next != null && next.hasNext(); + } - IAEItemStack result = this.next.next(); - this.ensureItems(); - return result; - } + @Override + public IAEItemStack next() + { + if( this.next == null ) + { + throw new NoSuchElementException(); + } + if( this.version != this.parentVersion.get() ) + { + throw new ConcurrentModificationException(); + } - private void ensureItems() { - if (hasNext()) { - return; // Still items left in the current one - } + IAEItemStack result = this.next.next(); + this.ensureItems(); + return result; + } - // Find the next iterator willing to return some items... - while (this.parent.hasNext()) { - this.next = this.parent.next().iterator(); + private void ensureItems() + { + if( hasNext() ) + { + return; // Still items left in the current one + } - if (this.next.hasNext()) { - return; // Found one! - } - } + // Find the next iterator willing to return some items... + while ( this.parent.hasNext() ) + { + this.next = this.parent.next().iterator(); - // No more items - this.next = null; - } - } + if( this.next.hasNext() ) + { + return; // Found one! + } + } + + // No more items + this.next = null; + } + } }