diff --git a/src/main/java/appeng/crafting/CraftingJob.java b/src/main/java/appeng/crafting/CraftingJob.java index 9e43cd323..b25bc844c 100644 --- a/src/main/java/appeng/crafting/CraftingJob.java +++ b/src/main/java/appeng/crafting/CraftingJob.java @@ -59,7 +59,6 @@ public class CraftingJob implements Runnable, ICraftingJob private final IItemList crafting = AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList(); private final IItemList missing = AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList(); private final IItemList usedWhileBuilding = AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList(); - private final IItemList uniques = AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList(); private final HashMap opsAndMultiplier = new HashMap<>(); private final Object monitor = new Object(); @@ -114,11 +113,6 @@ public class CraftingJob implements Runnable, ICraftingJob return usedWhileBuilding; } - public IItemList getUniques() - { - return uniques; - } - IAEItemStack checkUse( final IAEItemStack available ) { return this.availableCheck.extractItems( available, Actionable.MODULATE, this.actionSrc ); diff --git a/src/main/java/appeng/crafting/CraftingTreeNode.java b/src/main/java/appeng/crafting/CraftingTreeNode.java index 0ab5942a4..a43f02edf 100644 --- a/src/main/java/appeng/crafting/CraftingTreeNode.java +++ b/src/main/java/appeng/crafting/CraftingTreeNode.java @@ -88,7 +88,7 @@ public class CraftingTreeNode for( final ICraftingPatternDetails details : cc.getCraftingFor( this.what, this.parent == null ? null : this.parent.details, slot, this.world ) )// in // order. { - if( this.parent == null || this.parent.notRecursive() ) + if( this.parent == null || notRecursive( details ) && this.parent.details != details ) { this.nodes.add( new CraftingTreeProcess( cc, job, details, this, depth + 1 ) ); } @@ -119,7 +119,7 @@ public class CraftingTreeNode { Collection itemList = new ArrayList<>(); - if( this.what.getItem().hasContainerItem( this.what.getDefinition() ) ) + if( this.what.getItem().hasContainerItem( this.what.getDefinition() ) || this.what.getItem().isDamageable() ) { itemList.addAll( inventoryList.findFuzzy( this.what, FuzzyMode.IGNORE_ALL ) ); @@ -298,9 +298,17 @@ public class CraftingTreeNode throw new CraftBranchFailure( this.what, l ); } - boolean notRecursive() + boolean notRecursive( ICraftingPatternDetails details ) { - return this.parent == null || job.getUniques().findPrecise( this.what ) == null; + if( this.parent == null ) + { + return true; + } + if( this.parent.details == details ) + { + return false; + } + return this.parent.notRecursive( details ); } void dive( final CraftingJob job ) diff --git a/src/main/java/appeng/crafting/CraftingTreeProcess.java b/src/main/java/appeng/crafting/CraftingTreeProcess.java index eb28a7f25..058bea92e 100644 --- a/src/main/java/appeng/crafting/CraftingTreeProcess.java +++ b/src/main/java/appeng/crafting/CraftingTreeProcess.java @@ -24,19 +24,16 @@ 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 it.unimi.dsi.fastutil.objects.Object2LongArrayMap; import net.minecraft.item.ItemStack; import net.minecraft.world.World; -import appeng.api.AEApi; import appeng.api.config.Actionable; import appeng.api.networking.crafting.ICraftingGrid; import appeng.api.networking.crafting.ICraftingPatternDetails; import appeng.api.networking.security.IActionSource; -import appeng.api.storage.channels.IItemStorageChannel; import appeng.api.storage.data.IAEItemStack; import appeng.api.storage.data.IItemList; import appeng.me.cluster.implementations.CraftingCPUCluster; @@ -75,14 +72,18 @@ public class CraftingTreeProcess } final IAEItemStack[] list = details.getInputs(); - for( final IAEItemStack part : details.getCondensedInputs() ) + for( final IAEItemStack part : details.getCondensedOutputs() ) { - if( part.getItem().hasContainerItem( part.getDefinition() ) ) + for( final IAEItemStack o : details.getCondensedInputs() ) { - this.limitQty = true; - //break; + if( part.equals( o ) ) + { + this.limitQty = true; + break; + } } } + // this is minor different then below, this slot uses the pattern, but kinda fudges it. for( IAEItemStack part : details.getCondensedInputs() ) { @@ -99,21 +100,57 @@ public class CraftingTreeProcess if( part.getItem().hasContainerItem( part.getDefinition() ) ) { part = list[x]; + this.limitQty = true; isPartContainer = true; } - long wantedSize = part.getStackSize(); - if( isPartContainer && wantedSize > 0 ) + long wantedSize = part.getStackSize(); + IAEItemStack found; + IAEItemStack used; + long remaining = 0; + long requestAmount = 0; + + if( wantedSize > 0 ) { - if( details.canSubstitute() && cc.getCraftingFor( part, details, x, world ).isEmpty() ) + if( details.canSubstitute() ) { - IItemList aa = ( (CraftingGridCache) cc ).getAvailableItems( AEApi.instance().storage().getStorageChannel( IItemStorageChannel.class ).createList() ); - for( IAEItemStack is : aa ) + for( IAEItemStack subs : details.getSubstituteInputs( x ) ) { - if( is.fuzzyComparison( part, FuzzyMode.IGNORE_ALL ) ) + found = job.checkAvailable( subs ); + + if( found != null ) { - wantedSize -= 1; - this.nodes.put( new CraftingTreeNode( cc, job, is.copy().setStackSize( 1 ), this, x, depth + 1 ), 1 ); + used = job.getUsedWhileBuilding().findPrecise( subs ); + remaining = found.getStackSize(); + if( used != null ) + { + if( used.getStackSize() >= found.getStackSize() ) + { + remaining = 0; + } + else + { + remaining -= used.getStackSize(); + } + } + } + + if( remaining > 0 ) + { + if( remaining >= wantedSize ) + { + requestAmount = wantedSize; + wantedSize = 0; + //we have the items + } + else + { + requestAmount = remaining; + wantedSize -= remaining; + } + subs = subs.copy().setStackSize( requestAmount ); + job.getUsedWhileBuilding().addStorage( subs ); + this.nodes.put( new CraftingTreeNode( cc, job, subs, this, x, depth + 1 ), requestAmount ); if( wantedSize == 0 ) { break; @@ -121,144 +158,98 @@ public class CraftingTreeProcess } } } - } - - if( !isPartContainer ) - { - IAEItemStack found = job.checkAvailable( part ); - IAEItemStack used; - long requestAmount; - - long remaining = 0; - if( found != null ) + else { - used = job.getUsedWhileBuilding().findPrecise( part ); - remaining = found.getStackSize(); - if( used != null ) + found = job.checkAvailable( part ); + + if( found != null ) { - if( used.getStackSize() >= found.getStackSize() ) + used = job.getUsedWhileBuilding().findPrecise( part ); + remaining = found.getStackSize(); + if( used != null ) { - remaining = 0; + if( used.getStackSize() >= found.getStackSize() ) + { + remaining = 0; + } + else + { + remaining -= used.getStackSize(); + } + } + } + + if( remaining > 0 ) + { + if( remaining >= wantedSize ) + { + requestAmount = wantedSize; + wantedSize = 0; + //we have the items } else { - remaining -= used.getStackSize(); + requestAmount = remaining; + wantedSize -= remaining; } + part = part.copy().setStackSize( requestAmount ); + job.getUsedWhileBuilding().addStorage( part ); + this.nodes.put( new CraftingTreeNode( cc, job, part, this, x, depth + 1 ), requestAmount ); } - } - - if( remaining > 0 ) - { - if( remaining >= wantedSize ) - { - requestAmount = wantedSize; - wantedSize = 0; - //we have the items - } - else - { - requestAmount = remaining; - wantedSize -= remaining; - } - part = part.copy().setStackSize( requestAmount ); - job.getUsedWhileBuilding().addStorage( part ); - this.nodes.put( new CraftingTreeNode( cc, job, part, this, x, depth + 1 ), requestAmount ); if( wantedSize == 0 ) { break; } } - if( wantedSize > 0 ) + + if( details.canSubstitute() && cc.getCraftingFor( part, details, x, world ).isEmpty() ) { - if( details.canSubstitute() && cc.getCraftingFor( part, details, x, world ).isEmpty() ) + for( IAEItemStack subs : details.getSubstituteInputs( x ) ) { - //try to extract substitutes - for( IAEItemStack subs : details.getSubstituteInputs( x ) ) + if( subs.fuzzyComparison( part, FuzzyMode.IGNORE_ALL ) ) { - if( subs.equals( part ) ) - { - continue; - } - found = job.checkAvailable( subs ); - remaining = 0; - - if( found != null ) - { - used = job.getUsedWhileBuilding().findPrecise( subs ); - remaining = found.getStackSize(); - if( used != null ) - { - if( used.getStackSize() >= found.getStackSize() ) - { - remaining = 0; - } - else - { - remaining -= used.getStackSize(); - } - } - } - - if( remaining > 0 ) - { - if( remaining >= wantedSize ) - { - requestAmount = wantedSize; - wantedSize = 0; - //we have the items - } - else - { - requestAmount = remaining; - wantedSize -= remaining; - } - subs = subs.copy().setStackSize( requestAmount ); - job.getUsedWhileBuilding().addStorage( subs ); - this.nodes.put( new CraftingTreeNode( cc, job, subs, this, x, depth + 1 ), requestAmount ); - } + wantedSize -= 1; + this.nodes.put( new CraftingTreeNode( cc, job, subs.copy().setStackSize( 1 ), this, x, depth + 1 ), 1 ); if( wantedSize == 0 ) { break; } } - if( wantedSize > 0 ) + } + //try to order the crafting of a substitute + ICraftingPatternDetails prioritizedPattern = null; + IAEItemStack prioritizedIAE = null; + for( IAEItemStack subs : details.getSubstituteInputs( x ) ) + { + if( subs.equals( part ) ) { - //try to order the crafting of a substitute - ICraftingPatternDetails prioritizedPattern = null; - IAEItemStack prioritizedIAE = null; - for( IAEItemStack subs : details.getSubstituteInputs( x ) ) - { - if( subs.equals( part ) ) - { - continue; - } - ImmutableCollection detailCollection = cc.getCraftingFor( subs, details, x, world ); - - for( ICraftingPatternDetails sp : detailCollection ) - { - if( prioritizedPattern == null ) - { - prioritizedPattern = sp; - prioritizedIAE = subs; - } - else - { - if( sp.getPriority() > prioritizedPattern.getPriority() ) - { - prioritizedPattern = sp; - prioritizedIAE = subs; - } - } - this.nodes.put( new CraftingTreeNode( cc, job, prioritizedIAE.copy(), this, x, depth + 1 ), wantedSize ); - wantedSize = 0; - break; - } - } - if( wantedSize == 0 ) - { - break; - } + continue; } + ImmutableCollection detailCollection = cc.getCraftingFor( subs, details, x, world ); + + for( ICraftingPatternDetails sp : detailCollection ) + { + if( prioritizedPattern == null ) + { + prioritizedPattern = sp; + prioritizedIAE = subs; + } + else + { + if( sp.getPriority() > prioritizedPattern.getPriority() ) + { + prioritizedPattern = sp; + prioritizedIAE = subs; + } + } + this.nodes.put( new CraftingTreeNode( cc, job, prioritizedIAE.copy(), this, x, depth + 1 ), wantedSize ); + wantedSize = 0; + break; + } + } + if( wantedSize == 0 ) + { + break; } } } @@ -276,23 +267,11 @@ public class CraftingTreeProcess } } } - - for( final IAEItemStack part : details.getCondensedOutputs() ) - { - for( final IAEItemStack o : details.getCondensedInputs() ) - { - if( part.equals( o ) ) - { - this.limitQty = true; - break; - } - } - } } - boolean notRecursive() + boolean notRecursive( ICraftingPatternDetails details ) { - return this.parent == null || this.parent.notRecursive(); + return this.parent == null || this.parent.notRecursive( details ); } long getTimes( final long remaining, final long stackSize ) diff --git a/src/main/java/appeng/parts/misc/ItemHandlerAdapter.java b/src/main/java/appeng/parts/misc/ItemHandlerAdapter.java index a55b6c746..77267e07f 100644 --- a/src/main/java/appeng/parts/misc/ItemHandlerAdapter.java +++ b/src/main/java/appeng/parts/misc/ItemHandlerAdapter.java @@ -147,52 +147,41 @@ class ItemHandlerAdapter implements IMEInventory, IBaseMonitor + // maxSize, even if we request more. So even if it returns a valid stack, it might have more stuff. + do { - 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 ); - + extracted = this.itemHandler.extractItem( i, remainingCurrentSlot, simulate ); if( !extracted.isEmpty() ) { - extracted.setCount( Math.min( stackInInventorySlot.getCount(), remainingSize ) ); + // In order to guard against broken IItemHandler implementations, we'll try to guess if the returned + // stack (especially in simulate mode) is the same that was returned by getStackInSlot. This is + // obviously not a precise science, but it would catch the previous Forge bug: + // https://github.com/MinecraftForge/MinecraftForge/pull/6580 + if( extracted == stackInInventorySlot ) + { + extracted = extracted.copy(); + } + + 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 ); + } + + // Heuristic for simulation: looping in case of simulations is pointless, since the state of the + // underlying inventory does not change after a simulated extraction. To still support inventories + // that report stacks that are larger than maxStackSize, we use this heuristic + if( simulate && extracted.getCount() == extracted.getMaxStackSize() && remainingCurrentSlot > extracted.getMaxStackSize() ) + { + extracted.setCount( remainingCurrentSlot ); + } + if( gathered.isEmpty() ) { gathered = extracted; @@ -201,9 +190,11 @@ class ItemHandlerAdapter implements IMEInventory, IBaseMonitor 0 ); + + remainingSize -= stackSizeCurrentSlot - remainingCurrentSlot; 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 22cbf20e7..d0b4b5730 100644 --- a/src/main/java/appeng/util/item/AEItemStack.java +++ b/src/main/java/appeng/util/item/AEItemStack.java @@ -299,6 +299,7 @@ public final class AEItemStack extends AEStack implements IAEItemS { // We need a new stack :-( itemStack = this.createItemStack(); + itemStack.setCount( Ints.saturatedCast( stackSize ) ); } return itemStack; }