Merge branch 'master' into feat/crafting-cpu-camera-aim

This commit is contained in:
Worive authored and GitHub committed 2026-10-25 14:30:06 +01:00
commit bc8761c600
32 files changed
+578 -142

No files matched your search

@@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import appeng.api.networking.crafting.ICraftingPatternDetails;
import appeng.api.storage.data.IAEItemStack;
import appeng.crafting.v2.CraftingJobV2;
import appeng.crafting.v2.CraftingRequest;
@@ -712,4 +713,99 @@ public class CraftingV2Tests {
assertEquals(1, goldDustRequests.get(0).stack.getStackSize());
assertEquals(0, goldDustRequests.get(0).remainingToProcess);
}
private void addEqualPriorityPlateRecipes(MockAESystem aeSystem) {
aeSystem.newProcessingPattern().addInput(withSize(ironDust, 2)) //
.addOutput(withSize(ironIngot, 1)) //
.buildAndAdd();
aeSystem.newProcessingPattern().addInput(withSize(ironIngot, 1)) //
.addOutput(withSize(ironPlate, 1)) //
.buildAndAdd();
aeSystem.newProcessingPattern().addInput(withSize(goldDust, 1)) //
.addOutput(withSize(ironPlate, 1)) //
.buildAndAdd();
}
@Test
void competingRecipeSamePriority() {
MockAESystem aeSystem = new MockAESystem(dummyWorld);
aeSystem.addStoredItem(withSize(ironDust, 4));
aeSystem.addStoredItem(withSize(goldDust, 1));
addEqualPriorityPlateRecipes(aeSystem);
final CraftingJobV2 job = aeSystem.makeCraftingJob(withSize(ironPlate, 3));
simulateJobAndCheck(job, MAX_SIMULATION_STEPS);
assertFalse(job.isSimulation());
assertEquals(job.getOutput(), AEItemStack.create(withSize(ironPlate, 3)));
assertJobPlanEquals(
job,
AEItemStack.create(withSize(ironDust, 4)),
AEItemStack.create(withSize(goldDust, 1)),
AEItemStack.create(withSize(ironIngot, 0)).setCountRequestable(2),
AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(3));
final CraftingJobV2 jobFailed = aeSystem.makeCraftingJob(withSize(ironPlate, 4));
simulateJobAndCheck(jobFailed, MAX_SIMULATION_STEPS);
assertTrue(jobFailed.isSimulation());
assertJobPlanEquals(
jobFailed,
AEItemStack.create(withSize(ironDust, 6)),
AEItemStack.create(withSize(goldDust, 1)),
AEItemStack.create(withSize(ironIngot, 0)).setCountRequestable(3),
AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(4));
}
@Test
void competingRecipeSamePriorityIronDustOnly() {
MockAESystem aeSystem = new MockAESystem(dummyWorld);
aeSystem.addStoredItem(withSize(ironDust, 2));
addEqualPriorityPlateRecipes(aeSystem);
final CraftingJobV2 job = aeSystem.makeCraftingJob(withSize(ironPlate, 1));
simulateJobAndCheck(job, MAX_SIMULATION_STEPS);
assertFalse(job.isSimulation());
assertJobPlanEquals(
job,
AEItemStack.create(withSize(ironDust, 2)),
AEItemStack.create(withSize(ironIngot, 0)).setCountRequestable(1),
AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(1));
}
@Test
void competingRecipeSamePriorityGoldDustOnly() {
MockAESystem aeSystem = new MockAESystem(dummyWorld);
aeSystem.addStoredItem(withSize(goldDust, 1));
addEqualPriorityPlateRecipes(aeSystem);
final CraftingJobV2 job = aeSystem.makeCraftingJob(withSize(ironPlate, 1));
simulateJobAndCheck(job, MAX_SIMULATION_STEPS);
assertFalse(job.isSimulation());
assertJobPlanEquals(
job,
AEItemStack.create(withSize(goldDust, 1)),
AEItemStack.create(withSize(ironPlate, 0)).setCountRequestable(1));
}
@Test
void competingRecipePatternsListedByPriority() {
MockAESystem aeSystem = new MockAESystem(dummyWorld);
addEqualPriorityPlateRecipes(aeSystem);
aeSystem.newProcessingPattern().addInput(withSize(goldIngot, 1)) //
.addOutput(withSize(ironPlate, 1)) //
.setPriority(1) //
.buildAndAdd();
aeSystem.newProcessingPattern().addInput(withSize(ironDust, 1)) //
.addOutput(withSize(ironPlate, 1)) //
.setPriority(-1) //
.buildAndAdd();
aeSystem.cgCache.setMockPatternsFromMethods();
final List<ICraftingPatternDetails> platePatterns = new ArrayList<>(
aeSystem.cgCache.getCraftingFor(AEItemStack.create(withSize(ironPlate, 1)), null, 0, dummyWorld));
assertEquals(4, platePatterns.size());
assertEquals(1, platePatterns.get(0).getPriority());
assertEquals(0, platePatterns.get(1).getPriority());
assertEquals(0, platePatterns.get(2).getPriority());
assertEquals(-1, platePatterns.get(3).getPriority());
}
}
@@ -2,6 +2,7 @@ package appeng.test.me.crafting;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -202,6 +203,24 @@ public class CraftingCPUClusterFunctionalTest {
assertTrue(cpu.completed());
}
@Test
void breakingClusterWithInvalidatedCoreKeepsJob() {
cpu.prepare(pattern, 64, 128);
cpu.core.updateStatus(cpu);
cpu.core.invalidate();
cpu.breakCluster();
assertAll(
() -> assertTrue(cpu.isDestroyed()),
() -> assertFalse(cpu.completed(), "The job must not be cancelled"),
() -> assertEquals(512, cpu.storedInputs(), "The inventory must not be spilled"),
() -> assertEquals(
cpu.getInventory().writeInventory(),
cpu.core.getPreviousState().getTag("inventory"),
"The core must keep the job"));
}
private void tick() {
cpu.updateCraftingLogic(grid, energy, cache);
}
@@ -0,0 +1,35 @@
package appeng.test.tile.crafting;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import org.junit.jupiter.api.Test;
import appeng.tile.crafting.TileCraftingTile;
public class TileCraftingTileFunctionalTest {
@Test
void unformedCoreKeepsPendingJobAcrossSaves() {
final NBTTagList inventory = new NBTTagList();
inventory.appendTag(new ItemStack(Blocks.cobblestone, 64).writeToNBT(new NBTTagCompound()));
final NBTTagCompound saved = new NBTTagCompound();
saved.setBoolean("core", true);
saved.setTag("inventory", inventory);
final TileCraftingTile unformed = new TileCraftingTile();
unformed.readFromNBT(saved);
final NBTTagCompound resaved = new NBTTagCompound();
unformed.writeToNBT(resaved);
final TileCraftingTile reloaded = new TileCraftingTile();
reloaded.readFromNBT(resaved);
assertNotNull(reloaded.getPreviousState());
assertEquals(inventory, reloaded.getPreviousState().getTag("inventory"));
}
}
@@ -15,6 +15,9 @@ public interface IAdvancedLevelEmitter
void setReportingValue(int slot, long v);
/** Returns the amount of the slot's configured stack currently stored in the network. */
long getStoredAmount(int slot);
boolean isSlotActive(int slot);
void setSlotActive(int slot, boolean active);
@@ -21,6 +21,9 @@ public interface ILevelEmitter
long getReportingValue();
/** Returns the value the emitter currently compares against its threshold. */
long getStoredAmount();
default AEStackTypeFilter getTypeFilters() {
return new AEStackTypeFilter();
}
@@ -102,6 +102,7 @@ public class BlockCraftingUnit extends AEBaseTileBlock {
final TileCraftingTile cp = this.getTileEntity(w, x, y, z);
if (cp != null) {
cp.breakCluster();
cp.spillPreviousState();
}
super.breakBlock(w, x, y, z, a, b);
@@ -1,9 +1,11 @@
package appeng.client.gui.implementations;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.entity.player.InventoryPlayer;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import appeng.api.config.RedstoneMode;
import appeng.api.config.Settings;
@@ -14,7 +16,9 @@ import appeng.client.gui.AEBaseGui;
import appeng.client.gui.slots.VirtualMEPhantomSlot;
import appeng.client.gui.widgets.GuiImgButton;
import appeng.client.gui.widgets.MEGuiTextField;
import appeng.client.render.StackSizeRenderer;
import appeng.container.implementations.ContainerAdvancedLevelEmitter;
import appeng.core.AEConfig;
import appeng.core.localization.ColorUtils;
import appeng.core.localization.GuiText;
import appeng.util.calculators.ArithHelper;
@@ -100,7 +104,27 @@ public class GuiAdvancedLevelEmitter extends AEBaseGui {
SLOT_Y + y,
this.container.configSync,
slot,
GuiAdvancedLevelEmitter::acceptType));
GuiAdvancedLevelEmitter::acceptType) {
@Override
public boolean drawStackAndOverlay(final Minecraft mc, final int mouseX, final int mouseY) {
final boolean hovered = super.drawStackAndOverlay(mc, mouseX, mouseY);
if (this.getAEStack() != null) {
// the item icon is rendered with depth, so the text must skip the depth test
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_DEPTH_TEST);
StackSizeRenderer.drawStackSize(
this.getX(),
this.getY(),
GuiAdvancedLevelEmitter.this.container.getStoredAmount(s),
mc.fontRenderer,
AEConfig.instance.getTerminalFontSize());
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_DEPTH_TEST);
}
return hovered;
}
});
}
}
@@ -831,7 +831,8 @@ public class GuiInterfaceTerminal extends AEBaseGui
bindTexture(BACKGROUND);
final Tessellator tessellator = Tessellator.instance;
tessellator.startDrawingQuads();
int relY = 0;
// Preserve the full entry height even when rendering only visible rows.
int relY = entry.rows * 18;
final int slotLeftMargin = (VIEW_WIDTH - entry.rowSize * 18);
entry.dispY = viewY;
@@ -841,7 +842,8 @@ public class GuiInterfaceTerminal extends AEBaseGui
final int rowYTop = row * 18;
final int rowYBot = rowYTop + 18;
relY += 18;
// Scissoring hides offscreen rows but does not skip item rendering work.
if (viewY + rowYTop >= viewHeight) break;
/* Is the slot row in view? */
if (viewY + rowYBot <= titleBottom) {
continue;
@@ -896,6 +898,7 @@ public class GuiInterfaceTerminal extends AEBaseGui
for (int row = 0; row < entry.rows; ++row) {
final int rowYTop = row * 18;
final int rowYBot = rowYTop + 18;
if (viewY + rowYTop >= viewHeight) break;
/* Is the slot row in view? */
if (viewY + rowYBot <= titleBottom) {
continue;
@@ -1344,6 +1347,13 @@ public class GuiInterfaceTerminal extends AEBaseGui
if (w == null) {
return false;
}
// getOutputAE() already decoded this pattern while drawing its icon.
// Reuse that result rather than creating another PatternHelper here.
if (itemStack.getItem() instanceof ItemEncodedPattern iep && iep.hasCachedOutputAE(itemStack)) {
return false;
}
return !(itemStack.getItem() instanceof ICraftingPatternItem icpi
&& icpi.getPatternForItem(itemStack, w) != null);
}
@@ -13,6 +13,7 @@ package appeng.client.gui.implementations;
import java.util.IdentityHashMap;
import java.util.Map;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiButton;
import net.minecraft.entity.player.InventoryPlayer;
import net.minecraft.util.IIcon;
@@ -20,6 +21,7 @@ import net.minecraft.util.ResourceLocation;
import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.GL11;
import appeng.api.config.FuzzyMode;
import appeng.api.config.LevelType;
@@ -35,6 +37,7 @@ import appeng.client.gui.widgets.GuiImgButton;
import appeng.client.gui.widgets.GuiQuantityButton;
import appeng.client.gui.widgets.MEGuiTextField;
import appeng.client.gui.widgets.TypeToggleButton;
import appeng.client.render.StackSizeRenderer;
import appeng.container.implementations.ContainerLevelEmitter;
import appeng.core.AEConfig;
import appeng.core.localization.GuiText;
@@ -80,7 +83,29 @@ public class GuiLevelEmitter extends GuiUpgradeable {
this.container.setTextField(this.amountTextField);
this.validateText();
this.config = new VirtualMEPhantomSlot(17, 42, this.container.configSync, 0, GuiLevelEmitter::acceptType);
this.config = new VirtualMEPhantomSlot(17, 42, this.container.configSync, 0, GuiLevelEmitter::acceptType) {
@Override
public boolean drawStackAndOverlay(final Minecraft mc, final int mouseX, final int mouseY) {
final boolean hovered = super.drawStackAndOverlay(mc, mouseX, mouseY);
// in energy mode or with a crafting card the stored value is not an item amount
if (this.getAEStack() != null && GuiLevelEmitter.this.container.getLevelMode() == LevelType.ITEM_LEVEL
&& GuiLevelEmitter.this.bc.getInstalledUpgrades(Upgrades.CRAFTING) == 0) {
// the item icon is rendered with depth, so the text must skip the depth test
GL11.glDisable(GL11.GL_LIGHTING);
GL11.glDisable(GL11.GL_DEPTH_TEST);
StackSizeRenderer.drawStackSize(
this.getX(),
this.getY(),
GuiLevelEmitter.this.container.getStoredAmount(),
mc.fontRenderer,
AEConfig.instance.getTerminalFontSize());
GL11.glEnable(GL11.GL_LIGHTING);
GL11.glEnable(GL11.GL_DEPTH_TEST);
}
return hovered;
}
};
this.registerVirtualSlots(this.config);
}
@@ -18,9 +18,10 @@ import appeng.api.parts.IPartHost;
import appeng.api.util.AECableType;
import appeng.api.util.AEColor;
import appeng.me.helpers.AENetworkProxy;
import appeng.me.helpers.IGridProxyable;
import appeng.parts.networking.PartCable;
import appeng.tile.grid.AENetworkInvTile;
import appeng.tile.grid.AENetworkPowerTile;
import appeng.tile.crafting.TileCraftingTile;
import appeng.tile.spatial.TileSpatialPylon;
public class RendererCable extends AbstractRendererPreview implements IRenderPreview {
@@ -233,12 +234,20 @@ public class RendererCable extends AbstractRendererPreview implements IRenderPre
AEColor gridHostColor = gridHost instanceof IColorableTile ? ((IColorableTile) gridHost).getColor()
: AEColor.Transparent;
if (gridHost instanceof AENetworkInvTile) {
return hasConnectableSide(((AENetworkInvTile) gridHost)::getProxy, side, cableColor, gridHostColor);
if (gridHost instanceof TileCraftingTile craftingTile) {
return craftingTile.isFormed() && connectionType != null
&& connectionType != AECableType.NONE
&& isColorCompatible(cableColor, gridHostColor);
}
if (gridHost instanceof AENetworkPowerTile) {
return hasConnectableSide(((AENetworkPowerTile) gridHost)::getProxy, side, cableColor, gridHostColor);
if (gridHost instanceof TileSpatialPylon spatialPylon) {
return (spatialPylon.getDisplayBits() & TileSpatialPylon.MB_STATUS) != 0 && connectionType != null
&& connectionType != AECableType.NONE
&& isColorCompatible(cableColor, gridHostColor);
}
if (gridHost instanceof IGridProxyable proxyable && proxyable.getProxy() != null) {
return hasConnectableSide(proxyable::getProxy, side, cableColor, gridHostColor);
}
return connectionType != null && connectionType != AECableType.NONE
@@ -33,6 +33,7 @@ public class ContainerAdvancedLevelEmitter extends AEBaseContainer implements IC
private MEGuiTextField[] textFields;
private final LongSyncHandler[] amountSync = new LongSyncHandler[IAdvancedLevelEmitter.SLOT_COUNT];
private final LongSyncHandler[] storedSync = new LongSyncHandler[IAdvancedLevelEmitter.SLOT_COUNT];
private final BooleanSyncHandler[] activeSync = new BooleanSyncHandler[IAdvancedLevelEmitter.SLOT_COUNT];
private final BooleanSyncHandler[] invertedSync = new BooleanSyncHandler[IAdvancedLevelEmitter.SLOT_COUNT];
private final ConfigEnumSyncHandler<BooleanOperation> logicModeSync;
@@ -55,6 +56,8 @@ public class ContainerAdvancedLevelEmitter extends AEBaseContainer implements IC
}
}).onServerChange((oldValue, newValue) -> this.lvlEmitter.setReportingValue(slot, newValue));
this.storedSync[slot] = sync.longS2C("stored" + slot);
this.activeSync[slot] = sync.booleanSync("active" + slot)
.onServerChange((oldValue, newValue) -> this.lvlEmitter.setSlotActive(slot, newValue));
@@ -92,6 +95,7 @@ public class ContainerAdvancedLevelEmitter extends AEBaseContainer implements IC
if (Platform.isServer()) {
for (int slot = 0; slot < IAdvancedLevelEmitter.SLOT_COUNT; slot++) {
this.amountSync[slot].set(this.lvlEmitter.getReportingValue(slot));
this.storedSync[slot].set(this.lvlEmitter.getStoredAmount(slot));
this.activeSync[slot].set(this.lvlEmitter.isSlotActive(slot));
this.invertedSync[slot].set(this.lvlEmitter.isSlotInverted(slot));
}
@@ -109,6 +113,10 @@ public class ContainerAdvancedLevelEmitter extends AEBaseContainer implements IC
this.amountSync[slot].set(value);
}
public long getStoredAmount(final int slot) {
return this.storedSync[slot].get();
}
public boolean isSlotActive(final int slot) {
return this.activeSync[slot].get();
}
@@ -55,6 +55,7 @@ public class ContainerLevelEmitter extends ContainerUpgradeable implements ICont
private MEGuiTextField textField;
private final LongSyncHandler emitterValueSync;
private final LongSyncHandler storedSync;
private final DeltaObjectSyncHandler<@NotNull AEStackTypeFilter, TypeFilterDelta> typeFiltersSync;
private final ConfigEnumSyncHandler<@NotNull LevelType> levelModeSync;
private final ConfigEnumSyncHandler<@NotNull YesNo> craftingModeSync;
@@ -72,6 +73,8 @@ public class ContainerLevelEmitter extends ContainerUpgradeable implements ICont
}
}).onServerChange((oldValue, newValue) -> this.lvlEmitter.setReportingValue(newValue));
this.storedSync = sync.longS2C("stored");
this.typeFiltersSync = sync
.object("typeFilters", AEStackTypeFilterSyncCodec.INSTANCE, this.lvlEmitter.getTypeFilters())
.onClientChange((oldValue, newValue) -> {
@@ -170,6 +173,7 @@ public class ContainerLevelEmitter extends ContainerUpgradeable implements ICont
if (Platform.isServer()) {
this.emitterValueSync.set(this.lvlEmitter.getReportingValue());
this.storedSync.set(this.lvlEmitter.getStoredAmount());
this.typeFiltersSync.set(this.lvlEmitter.getTypeFilters());
this.craftingModeSync.syncFromConfig();
this.levelModeSync.syncFromConfig();
@@ -185,6 +189,10 @@ public class ContainerLevelEmitter extends ContainerUpgradeable implements ICont
return this.emitterValueSync.get();
}
public long getStoredAmount() {
return this.storedSync.get();
}
public void setLevel(final long l) {
this.emitterValueSync.set(l);
}
@@ -60,6 +60,8 @@ public enum PlayerMessages implements Localization {
ItemFlowNotFound,
PatternRepeaterProvider,
PatternRepeaterAccessor,
PatternRepeaterRouteOutputTrue,
PatternRepeaterRouteOutputFalse,
WirelessHighlighted,
WirelessInOtherDim,
@@ -59,7 +59,9 @@ public enum WailaText implements Localization {
Waiting,
Provider,
Accessor;
Accessor,
RouteOutputTrue,
RouteOutputFalse;
private final String formatedName;
@@ -144,6 +144,18 @@ public class CraftableItemResolver implements CraftingRequestResolver {
if (matchingOutput == null) {
state = State.FAILURE;
}
// Complex patterns are calculated per slot, so condensation can hide an invalid stack size.
if (hasZeroSizedStack(isComplex ? pattern.getAEInputs() : this.patternInputs)
|| hasZeroSizedStack(this.patternRecursionInputs)
|| hasZeroSizedStack(isComplex ? pattern.getAEOutputs() : this.patternOutputs)) {
// 0-sized stacks can only come from malformed pattern data; they divide by zero in Platform.ceilDiv
// and in calculateOneStep. Refuse the pattern instead of cancelling the entire crafting job.
AELog.error(
"Refusing malformed pattern with a 0-sized stack, outputs: %s",
Arrays.toString(this.patternOutputs));
state = State.FAILURE;
}
}
@SuppressWarnings("unused")
@@ -176,6 +188,16 @@ public class CraftableItemResolver implements CraftingRequestResolver {
}
}
private static boolean hasZeroSizedStack(IAEStack<?>[] stacks) {
for (IAEStack<?> stack : stacks) {
if (stack != null && stack.getStackSize() <= 0) {
return true;
}
}
return false;
}
private static IAEStack<?>[] calculateRecursiveInputs(IAEStack<?>[] pInputs, IAEStack<?>[] pOutputs) {
IAEStack<?>[] recInputs = null;
// While this is a O(n*m) algorithm, no allocations are needed except for the actual output.
@@ -145,13 +145,8 @@ public class P2PTests {
.thenExecute("power the redstone P2P input", () -> {
unpoweredOutputStaysLow.disable();
setRedstoneInput(helper, 15);
}).thenWaitUntil("wait for the redstone P2P output to become powered", 40, () -> {
assertLinkedPair(helper, input, output, REDSTONE_FREQUENCY);
assertRedstonePower(helper, 15);
}).thenExecute("remove power from the redstone P2P input", () -> setRedstoneInput(helper, 0))
.thenWaitUntil("wait for the redstone P2P output to return to zero", 40, () -> {
assertCarrierActive(helper, controller);
assertLinkedPair(helper, input, output, REDSTONE_FREQUENCY);
setRedstoneInput(helper, 0);
assertRedstonePower(helper, 0);
}).thenExecute("begin restored-low invariant", unpoweredOutputStaysLow::enable).thenIdle(5)
.thenExecute("finish restored-low observation", unpoweredOutputStaysLow::disable).thenSucceed();
@@ -475,7 +470,7 @@ public class P2PTests {
private static void assertRedstonePower(GameTestHelper helper, int expectedPower) {
TestPos probe = helper.absolute(REDSTONE_PROBE_LABEL);
int actualPower = helper.getWorld().getStrongestIndirectPower(probe.x(), probe.y(), probe.z());
int actualPower = helper.getWorld().getBlockMetadata(probe.x(), probe.y(), probe.z());
helper.assertEquals(
expectedPower,
actualPower,
@@ -607,6 +607,60 @@ public class IOPortTests {
}).thenSucceed();
}
@GameTest(template = "ioport", timeoutTicks = 60)
@MethodSource("blockedRedstoneModes")
public static void removingRedstoneCardResumesWork(GameTestHelper helper, RedstoneMode mode, int strength) {
TileIOPort ioport = getIOPort(helper);
getDrive(helper);
TickCallbackHandle blockedCellStaysInInput = helper
.onEachTickDisabled("redstone control keeps the cell in input", () -> {
helper.assertNotNull(ioport.getStackInSlot(0), "Blocked cell should remain in input");
helper.assertNull(ioport.getStackInSlot(6), "Blocked cell should not reach output");
});
helper.startSequence()
.thenWaitUntilAtEnd("wait for IO port network activation", () -> assertIOPortActive(helper, ioport))
.thenIdle(1).thenExecuteAtStart("configure a redstone-blocked IO port", () -> {
setRedstoneInput(helper, strength);
installRedstoneUpgrade(ioport);
installUpgrade(
ioport,
AEApi.instance().definitions().materials().cardSuperluminalSpeed().maybeStack(1).get(),
1);
configureRedstone(ioport, mode);
helper.setSlot(IO_PORT_LABEL, 0, cell1k());
blockedCellStaysInInput.enable();
}).thenIdle(5).thenExecute("remove the redstone card", () -> {
blockedCellStaysInInput.disable();
ioport.getInventoryByName("upgrades").decrStackSize(0, 1);
}).thenWaitUntil("wait for processing without the redstone card", 20, () -> {
helper.assertNull(ioport.getStackInSlot(0), "Removing the card should unblock the input cell");
helper.assertNotNull(
ioport.getStackInSlot(6),
"The cell should reach output without a signal change");
helper.assertTrue(
ioport.getConfigManager().getSetting(Settings.REDSTONE_CONTROLLED) == mode,
"Removing the card should preserve the configured redstone mode");
helper.assertEquals(
1,
ioport.getInstalledUpgrades(Upgrades.SUPERLUMINALSPEED),
"The superluminal card should remain installed");
}).thenExecute("reinsert the redstone card and another cell", () -> {
helper.clearSlot(IO_PORT_LABEL, 6);
installRedstoneUpgrade(ioport);
helper.setSlot(IO_PORT_LABEL, 0, cell1k());
blockedCellStaysInInput.enable();
}).thenIdle(5).thenExecute("finish restored redstone control check", blockedCellStaysInInput::disable)
.thenSucceed();
}
public static List<GameTestArguments> blockedRedstoneModes() {
return Arrays.asList(
GameTestArguments.named("high-signal", RedstoneMode.HIGH_SIGNAL, 0),
GameTestArguments.named("low-signal", RedstoneMode.LOW_SIGNAL, 15),
GameTestArguments.named("pulse", RedstoneMode.SIGNAL_PULSE, 0));
}
// Runs in LOW_SIGNAL mode only after redstone power is removed.
@GameTest(template = "ioport", timeoutTicks = 60)
public static void redstoneLowSignalRunsOnlyWithoutPower(GameTestHelper helper) {
@@ -1,5 +1,9 @@
package appeng.integration.modules.waila.part;
import static appeng.parts.misc.PartPatternRepeater.NBT_MODE;
import static appeng.parts.misc.PartPatternRepeater.NBT_ROUTE_OUTPUT;
import static appeng.parts.misc.PartPatternRepeater.NBT_WAITING_STACKS;
import java.util.List;
import net.minecraft.entity.player.EntityPlayerMP;
@@ -19,15 +23,13 @@ import mcp.mobius.waila.api.IWailaDataAccessor;
public class PartPatternRepeaterDataProvider extends BasePartWailaDataProvider {
private static final String NBT_MODE = "provider";
private static final String NBT_WAITING_STACKS = "waitingStacks";
@Override
public List<String> getWailaBody(IPart part, List<String> currentToolTip, IWailaDataAccessor accessor,
IWailaConfigHandler config) {
if (part instanceof PartPatternRepeater) {
if (accessor.getNBTData().hasKey(NBT_MODE)) {
currentToolTip.add(accessor.getNBTData().getString(NBT_MODE));
currentToolTip.add(accessor.getNBTData().getString(NBT_ROUTE_OUTPUT));
if (accessor.getNBTData().hasKey(NBT_WAITING_STACKS)) {
currentToolTip.add(WailaText.Waiting.getLocal());
@@ -48,6 +50,9 @@ public class PartPatternRepeaterDataProvider extends BasePartWailaDataProvider {
int x, int y, int z) {
if (part instanceof PartPatternRepeater ppr) {
tag.setString(NBT_MODE, ppr.isProvider() ? WailaText.Provider.getLocal() : WailaText.Accessor.getLocal());
tag.setString(
NBT_ROUTE_OUTPUT,
ppr.isRouteOutput() ? WailaText.RouteOutputTrue.getLocal() : WailaText.RouteOutputFalse.getLocal());
IItemList<IAEStack<?>> waitingStacks = ppr.getWaitingStacks();
if (!waitingStacks.isEmpty()) {
@@ -281,6 +281,14 @@ public class ItemEncodedPattern extends AEBaseItem implements ICraftingPatternIt
return output;
}
/**
* A cached pattern output means the pattern was already decoded successfully. The Interface Terminal uses this to
* avoid decoding it again for validation.
*/
public boolean hasCachedOutputAE(final ItemStack item) {
return OUTPUT_STACK_CACHE.containsKey(item);
}
private boolean addInformation(final IAEStack<?>[] items, final List<String> lines, String label) {
final ItemStack unknownItem = new ItemStack(Blocks.fire);
boolean first = true;
+8 -4
View File
@@ -20,12 +20,12 @@ import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
@@ -357,7 +357,7 @@ public class CraftingGridCache
craftableItemSubstitutes.put(ais, details);
}
Set<ICraftingPatternDetails> methods = tmpCraft.computeIfAbsent(out, k -> new TreeSet<>(COMPARATOR));
Set<ICraftingPatternDetails> methods = tmpCraft.computeIfAbsent(out, k -> new LinkedHashSet<>());
methods.add(details);
}
@@ -367,10 +367,14 @@ public class CraftingGridCache
// make them immutable
for (final Entry<IAEStack<?>, Set<ICraftingPatternDetails>> e : tmpCraft.entrySet()) {
this.craftableItems.put(e.getKey(), ImmutableList.copyOf(e.getValue()));
// sort a list: a set ordered by priority alone would drop patterns of equal priority
final List<ICraftingPatternDetails> patterns = new ArrayList<>(e.getValue());
patterns.sort(Comparator.comparing(ICraftingPatternDetails::getPriority).reversed());
final ImmutableList<ICraftingPatternDetails> immutable = ImmutableList.copyOf(patterns);
this.craftableItems.put(e.getKey(), immutable);
final IAEItemStack ais = stackConvert(e.getKey());
if (ais != null) craftableItemsLegacy.put(ais, ImmutableList.copyOf(e.getValue()));
if (ais != null) craftableItemsLegacy.put(ais, immutable);
}
this.craftableItemsSnapshot = ImmutableMap.copyOf(this.craftableItems);
+1 -2
View File
@@ -152,11 +152,10 @@ public class GridStorageCache implements IStorageGrid {
}
if (machine instanceof IStackWatcherHost) {
final IStackWatcher myWatcher = this.watchers.get(machine);
final IStackWatcher myWatcher = this.watchers.remove(node);
if (myWatcher != null) {
myWatcher.clear();
this.watchers.remove(machine);
}
}
}
@@ -341,7 +341,7 @@ public class CraftingCPUCluster implements IAECluster, ICraftingCPU {
}
protected void addTile(final TileCraftingTile te) {
if (this.machineSrc == null || te.isCoreBlock()) {
if (this.machineSrc == null || te.isCoreBlock() && this.getCore().getPreviousState() == null) {
this.machineSrc = new MachineSource(te);
}
@@ -621,6 +621,18 @@ public class CraftingCPUCluster implements IAECluster, ICraftingCPU {
return (TileCraftingTile) this.machineSrc.via;
}
/// Whether the core tile, which persists this CPU's job, has been unloaded or replaced.
public boolean isCoreInvalid() {
return this.getCore().isInvalid();
}
/// Saves this CPU's job to its core, to be resumed once the core forms a cluster again.
public void storeJobInCore() {
final NBTTagCompound job = new NBTTagCompound();
this.writeToNBT(job);
this.getCore().setPreviousState(job);
}
public IGrid getGrid() {
IGrid node;
for (final TileCraftingTile r : this.tiles) {
@@ -722,6 +734,8 @@ public class CraftingCPUCluster implements IAECluster, ICraftingCPU {
}
this.usedStorage = 0;
this.remainingItemCount = 0;
this.startItemCount = 0;
this.isComplete = true;
this.myLastLink = null;
this.tasks.clear();
@@ -1647,6 +1661,10 @@ public class CraftingCPUCluster implements IAECluster, ICraftingCPU {
core.setPreviousState(null);
}
for (final TileCraftingTile te : this.tiles) {
te.spillPreviousState();
}
this.updateCPU();
this.updateName();
@@ -108,6 +108,11 @@ public class PartAdvancedLevelEmitter extends PartUpgradeable implements IAdvanc
this.updateState();
}
@Override
public long getStoredAmount(final int slot) {
return this.lastReportedValue[slot];
}
@Override
public boolean isSlotActive(final int slot) {
return this.slotActive[slot];
@@ -126,6 +126,11 @@ public class PartLevelEmitter extends PartUpgradeable implements ILevelEmitter {
return this.reportingValue;
}
@Override
public long getStoredAmount() {
return this.lastReportedValue;
}
public void setReportingValue(final long v) {
this.reportingValue = v;
this.getHost().markForSave();
@@ -1,13 +1,3 @@
/*
* This file is part of Applied Energistics 2. Copyright (c) 2013 - 2014, AlgorithmX2, 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 <http://www.gnu.org/licenses/lgpl>.
*/
package appeng.parts.misc;
import static appeng.util.Platform.convertStack;
@@ -83,6 +73,10 @@ import it.unimi.dsi.fastutil.objects.Object2BooleanOpenHashMap;
public class PartPatternRepeater extends PartBasicState
implements ICraftingWatcherHost, ICraftingProvider, IStorageInterceptor, ICraftingPostPatternChangeListener {
public static final String NBT_ROUTE_OUTPUT = "routeOutput";
public static final String NBT_MODE = "provider";
public static final String NBT_WAITING_STACKS = "waitingStacks";
private final Set<ICraftingPatternDetails> craftingList = new HashSet<>();
private final Object2BooleanMap<IAEStack<?>> emitableCrafting = new Object2BooleanOpenHashMap<>();
private IItemList<IAEStack<?>> waitingStacks = AEApi.instance().storage().createAEStackList();
@@ -90,6 +84,7 @@ public class PartPatternRepeater extends PartBasicState
private CraftingGridCache currentCraftingGrid = null;
private AENetworkProxy targetNetworkProxy = null;
private boolean provider = false;
private boolean routeOutput = true;
private PartPatternRepeater pairPatternRepeater = null;
private ICraftingWatcher myCraftingWatcher = null;
@@ -228,14 +223,16 @@ public class PartPatternRepeater extends PartBasicState
@Override
public void writeToNBT(NBTTagCompound data) {
data.setTag("waitingStacks", writeAEStackListNBT(this.waitingStacks));
data.setBoolean("provider", this.provider);
data.setTag(NBT_WAITING_STACKS, writeAEStackListNBT(this.waitingStacks));
data.setBoolean(NBT_MODE, this.provider);
data.setBoolean(NBT_ROUTE_OUTPUT, this.routeOutput);
}
@Override
public void readFromNBT(NBTTagCompound data) {
this.waitingStacks = readAEStackListNBT((NBTTagList) data.getTag("waitingStacks"));
this.provider = data.getBoolean("provider");
this.waitingStacks = readAEStackListNBT((NBTTagList) data.getTag(NBT_WAITING_STACKS));
this.provider = data.getBoolean(NBT_MODE);
if (data.hasKey(NBT_ROUTE_OUTPUT)) this.routeOutput = data.getBoolean(NBT_ROUTE_OUTPUT);
}
public boolean pushPattern(final ICraftingPatternDetails patternDetails, final InventoryCrafting table) {
@@ -257,19 +254,23 @@ public class PartPatternRepeater extends PartBasicState
if (pushRepeater.targetCraftingGrid != null
&& !visitedRepeaters.contains(pushRepeater.targetCraftingGrid)
&& pushRepeater.pushPatternToRepeater(patternDetails, table, visitedRepeaters)) {
for (IAEStack<?> outputStack : patternDetails.getCondensedAEOutputs()) {
waitingStacks.add(outputStack.copy());
if (this.routeOutput) {
for (IAEStack<?> outputStack : patternDetails.getCondensedAEOutputs()) {
waitingStacks.add(outputStack.copy());
}
this.addInterception();
}
this.addInterception();
return true;
}
} else {
if (medium.pushPattern(patternDetails, table)) {
for (IAEStack<?> outputStack : patternDetails.getCondensedAEOutputs()) {
waitingStacks.add(outputStack.copy());
if (this.routeOutput) {
for (IAEStack<?> outputStack : patternDetails.getCondensedAEOutputs()) {
waitingStacks.add(outputStack.copy());
}
this.addInterception();
}
this.addInterception();
return true;
}
@@ -368,7 +369,17 @@ public class PartPatternRepeater extends PartBasicState
if (Platform.isClient()) return true;
if (player.isSneaking()) {
this.waitingStacks.resetStatus();
this.routeOutput = !this.routeOutput;
if (this.routeOutput) {
player.addChatMessage(PlayerMessages.PatternRepeaterRouteOutputTrue.toChat());
this.addInterception();
} else {
player.addChatMessage(PlayerMessages.PatternRepeaterRouteOutputFalse.toChat());
this.waitingStacks.resetStatus();
}
this.getHost().markForSave();
return true;
}
@@ -388,6 +399,8 @@ public class PartPatternRepeater extends PartBasicState
this.gridChanged();
this.getHost().markForSave();
return true;
}
@@ -406,13 +419,14 @@ public class PartPatternRepeater extends PartBasicState
@Override
public boolean canAccept(IAEStack<?> stack) {
return !injecting && this.isRequesting(stack);
return this.routeOutput && !this.injecting && this.isRequesting(stack);
}
@Override
@SuppressWarnings({ "unchecked" })
public IAEStack<?> injectItems(final IAEStack<?> input, final Actionable type, final BaseActionSource src) {
if (input == null) return null;
if (!this.routeOutput) return input;
if (injecting) return input;
injecting = true;
@@ -452,10 +466,11 @@ public class PartPatternRepeater extends PartBasicState
@Override
public boolean shouldRemoveInterceptor(IAEStack<?> stack) {
return this.waitingStacks.isEmpty() && !this.emitableCrafting.containsValue(true);
return !this.routeOutput || (this.waitingStacks.isEmpty() && !this.emitableCrafting.containsValue(true));
}
private void addInterception() {
if (!this.routeOutput) return;
if (this.targetNetworkProxy == null) return;
List<IAEStackType<?>> types = new ArrayList<>(AEStackTypeRegistry.getAllTypes());
@@ -601,4 +616,8 @@ public class PartPatternRepeater extends PartBasicState
public void onRequestChange(final ICraftingGrid craftingGrid, IAEStack<?> what) {
this.updateEmitableStatus(what);
}
public boolean isRouteOutput() {
return this.routeOutput;
}
}
@@ -10,6 +10,9 @@
package appeng.parts.p2p;
import java.util.ArrayList;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.block.BlockRedstoneWire;
import net.minecraft.init.Blocks;
@@ -23,7 +26,6 @@ import appeng.api.networking.events.MENetworkChannelsChanged;
import appeng.api.networking.events.MENetworkEventSubscribe;
import appeng.api.networking.events.MENetworkPowerStatusChange;
import appeng.me.GridAccessException;
import appeng.util.Platform;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
@@ -73,15 +75,15 @@ public class PartP2PRedstone extends PartP2PTunnelNormal<PartP2PRedstone> {
final int yCoord = this.getTile().yCoord;
final int zCoord = this.getTile().zCoord;
Platform.notifyBlocksOfNeighbors(worldObj, xCoord, yCoord, zCoord);
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord, Blocks.air);
// and this cause sometimes it can go thought walls.
Platform.notifyBlocksOfNeighbors(worldObj, xCoord - 1, yCoord, zCoord);
Platform.notifyBlocksOfNeighbors(worldObj, xCoord, yCoord - 1, zCoord);
Platform.notifyBlocksOfNeighbors(worldObj, xCoord, yCoord, zCoord - 1);
Platform.notifyBlocksOfNeighbors(worldObj, xCoord, yCoord, zCoord + 1);
Platform.notifyBlocksOfNeighbors(worldObj, xCoord, yCoord + 1, zCoord);
Platform.notifyBlocksOfNeighbors(worldObj, xCoord + 1, yCoord, zCoord);
worldObj.notifyBlocksOfNeighborChange(xCoord - 1, yCoord, zCoord, Blocks.air);
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord - 1, zCoord, Blocks.air);
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord - 1, Blocks.air);
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord, zCoord + 1, Blocks.air);
worldObj.notifyBlocksOfNeighborChange(xCoord, yCoord + 1, zCoord, Blocks.air);
worldObj.notifyBlocksOfNeighborChange(xCoord + 1, yCoord, zCoord, Blocks.air);
}
@MENetworkEventSubscribe
@@ -161,7 +163,9 @@ public class PartP2PRedstone extends PartP2PTunnelNormal<PartP2PRedstone> {
private void sendToOutput(final int power) {
try {
for (final PartP2PRedstone rs : this.getOutputs()) {
List<PartP2PRedstone> outputs = new ArrayList<>();
this.getOutputs().forEach(outputs::add);
for (final PartP2PRedstone rs : outputs) {
rs.putInput(power);
}
} catch (final GridAccessException e) {
@@ -43,6 +43,6 @@ public abstract class PartP2PTunnelStatic<T extends PartP2PTunnelStatic> extends
@Override
protected boolean checkIfCompatibleType(final IPart testPart) {
return this.getClass().isInstance(testPart);
return testPart != null && testPart.getClass() == this.getClass();
}
}
@@ -17,10 +17,12 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList;
import net.minecraft.tileentity.TileEntity;
import net.minecraftforge.common.util.ForgeDirection;
@@ -184,8 +186,16 @@ public class TileCraftingTile extends AENetworkTile implements IAEMultiBlock, IP
@TileEvent(TileEventType.WORLD_NBT_WRITE)
public void writeToNBT_TileCraftingTile(final NBTTagCompound data) {
data.setBoolean("core", this.isCoreBlock());
if (this.isCoreBlock() && this.cluster != null) {
this.cluster.writeToNBT(data);
if (this.isCoreBlock()) {
if (this.cluster != null) {
this.cluster.writeToNBT(data);
} else if (this.previousState != null) {
for (final String key : (Set<String>) this.previousState.func_150296_c()) {
if (!data.hasKey(key)) {
data.setTag(key, this.previousState.getTag(key).copy());
}
}
}
}
}
@@ -249,80 +259,118 @@ public class TileCraftingTile extends AENetworkTile implements IAEMultiBlock, IP
}
public void breakCluster() {
if (this.cluster != null) {
this.cluster.cancel();
final MECraftingInventory inv = this.cluster.getInventory();
final LinkedList<WorldCoord> places = new LinkedList<>();
final Iterator<IGridHost> i = this.cluster.getTiles();
while (i.hasNext()) {
final IGridHost h = i.next();
if (h == this) {
places.add(new WorldCoord(this));
} else {
final TileEntity te = (TileEntity) h;
for (final ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
final WorldCoord wc = new WorldCoord(te);
wc.add(d, 1);
if (this.worldObj.isAirBlock(wc.x, wc.y, wc.z)) {
places.add(wc);
}
}
}
}
Collections.shuffle(places);
if (places.isEmpty()) {
throw new IllegalStateException(
this.cluster + " does not contain any kind of blocks, which were destroyed.");
}
for (IAEItemStack ais : (IItemList<IAEItemStack>) inv
.getAvailableItems(AEApi.instance().storage().createItemList())) {
ais = ais.copy();
ais.setStackSize(ais.getItemStack().getMaxStackSize());
while (!places.isEmpty()) {
final IAEItemStack g = inv.extractItems(ais.copy(), Actionable.MODULATE);
if (g == null) {
break;
}
final WorldCoord wc = places.removeFirst();
if (!AEConfig.instance.limitCraftingCPUSpill) {
places.add(wc);
}
Platform.spawnDrops(this.worldObj, wc.x, wc.y, wc.z, Collections.singletonList(g.getItemStack()));
}
if (places.isEmpty()) {
break;
}
}
for (IAEFluidStack ifs : (IItemList<IAEFluidStack>) inv
.getAvailableItems(AEApi.instance().storage().createFluidList())) {
while (!places.isEmpty()) {
final IAEItemStack g = stackConvertPacket(inv.extractItems(ifs, Actionable.MODULATE));
if (g == null) {
break;
}
final WorldCoord wc = places.removeFirst();
if (!AEConfig.instance.limitCraftingCPUSpill) {
places.add(wc);
}
Platform.spawnDrops(this.worldObj, wc.x, wc.y, wc.z, Collections.singletonList(g.getItemStack()));
}
if (places.isEmpty()) {
break;
}
}
if (this.cluster == null) {
return;
}
/*
* An invalidated core was unloaded or replaced (e.g. by a block mover) rather than broken, so the NBT it was
* saved to still holds the job and restores it once the core forms a cluster again. Cancelling and spilling the
* job here as well would duplicate it. The job is also kept on the core itself, for when the same tile is
* revalidated instead of being loaded from NBT.
*/
if (this.cluster.isCoreInvalid()) {
this.cluster.storeJobInCore();
this.cluster.destroy();
return;
}
this.cluster.cancel();
final LinkedList<WorldCoord> places = new LinkedList<>();
final Iterator<IGridHost> i = this.cluster.getTiles();
while (i.hasNext()) {
final IGridHost h = i.next();
if (h == this) {
places.add(new WorldCoord(this));
} else {
places.addAll(this.getAdjacentAir((TileEntity) h));
}
}
if (places.isEmpty()) {
throw new IllegalStateException(
this.cluster + " does not contain any kind of blocks, which were destroyed.");
}
this.spillInventory(this.cluster.getInventory(), places);
this.cluster.destroy();
}
/// Drops the items of the job loaded from NBT, for when no cluster resumes it.
public void spillPreviousState() {
if (this.previousState == null) {
return;
}
final MECraftingInventory inv = new MECraftingInventory();
inv.readInventory((NBTTagList) this.previousState.getTag("inventory"));
this.previousState = null;
final LinkedList<WorldCoord> places = this.getAdjacentAir(this);
if (places.isEmpty()) {
places.add(new WorldCoord(this));
}
this.spillInventory(inv, places);
}
private LinkedList<WorldCoord> getAdjacentAir(final TileEntity te) {
final LinkedList<WorldCoord> places = new LinkedList<>();
for (final ForgeDirection d : ForgeDirection.VALID_DIRECTIONS) {
final WorldCoord wc = new WorldCoord(te);
wc.add(d, 1);
if (this.worldObj.isAirBlock(wc.x, wc.y, wc.z)) {
places.add(wc);
}
}
return places;
}
private void spillInventory(final MECraftingInventory inv, final LinkedList<WorldCoord> places) {
Collections.shuffle(places);
for (IAEItemStack ais : (IItemList<IAEItemStack>) inv
.getAvailableItems(AEApi.instance().storage().createItemList())) {
ais = ais.copy();
ais.setStackSize(ais.getItemStack().getMaxStackSize());
while (!places.isEmpty()) {
final IAEItemStack g = inv.extractItems(ais.copy(), Actionable.MODULATE);
if (g == null) {
break;
}
final WorldCoord wc = places.removeFirst();
if (!AEConfig.instance.limitCraftingCPUSpill) {
places.add(wc);
}
Platform.spawnDrops(this.worldObj, wc.x, wc.y, wc.z, Collections.singletonList(g.getItemStack()));
}
if (places.isEmpty()) {
break;
}
}
for (IAEFluidStack ifs : (IItemList<IAEFluidStack>) inv
.getAvailableItems(AEApi.instance().storage().createFluidList())) {
while (!places.isEmpty()) {
final IAEItemStack g = stackConvertPacket(inv.extractItems(ifs, Actionable.MODULATE));
if (g == null) {
break;
}
final WorldCoord wc = places.removeFirst();
if (!AEConfig.instance.limitCraftingCPUSpill) {
places.add(wc);
}
Platform.spawnDrops(this.worldObj, wc.x, wc.y, wc.z, Collections.singletonList(g.getItemStack()));
}
if (places.isEmpty()) {
break;
}
}
}
@@ -311,7 +311,7 @@ public class TileIOPort extends AENetworkInvTile
|| slot == INPUT_SLOT_INDEX_BOTTOM_RIGHT)) {
moveQueue[slot] = 0;
}
if (this.cells == inv) {
if (this.cells == inv || this.upgrades == inv) {
this.updateTask();
}
}
@@ -358,8 +358,7 @@ public class TileIOPort extends AENetworkInvTile
if (!this.getProxy().isActive()) {
return TickRateModulation.IDLE;
}
final RedstoneMode rs = (RedstoneMode) this.manager.getSetting(Settings.REDSTONE_CONTROLLED);
if (rs == RedstoneMode.SIGNAL_PULSE && !this.pendingRedstonePulse) {
if (!this.isEnabled()) {
return TickRateModulation.IDLE;
}
@@ -7,16 +7,18 @@
"C": {"name": "appliedenergistics2:tile.BlockCreativeEnergyController", "meta": 1, "label": "ME Self-Powered Controller"},
"D": {"name": "appliedenergistics2:tile.BlockDrive", "meta": 0, "label": "ME Drive"},
"E": {"name": "minecraft:chest", "meta": 3, "label": "Chest"},
"F": {"name": "minecraft:hopper", "meta": 5, "label": "Hopper"}
"F": {"name": "minecraft:hopper", "meta": 5, "label": "Hopper"},
"G": {"name": "minecraft:redstone_wire", "meta": 0},
"H": {"name": "minecraft:stone", "meta": 0, "label": "Stone"}
},
"layers": [
[
".......",
".....H.",
"FAACAAE",
"......."
],
[
".......",
".....G.",
"EB...D.",
"......."
]
@@ -28,7 +30,7 @@
"input_tunnel_host": [1, 0, 1],
"main_network_power": [1, 1, 1],
"output_tunnel_host": [5, 0, 1],
"redstone_probe": [5, 0, 0],
"redstone_probe": [5, 1, 0],
"redstone_source": [1, 0, 0],
"remote_storage": [5, 1, 1],
"source_chest": [0, 1, 1],
@@ -155,6 +155,8 @@ chat.appliedenergistics2.ItemFlowInOtherDim=%s flow (%s) is located in dimension
chat.appliedenergistics2.ItemFlowNotFound=No recent network flow found for %s
chat.appliedenergistics2.PatternRepeaterProvider=§5Provider§r - provides the pattern to the current system from the §5Accessor§r in front.
chat.appliedenergistics2.PatternRepeaterAccessor=§5Accessor§r - provides the pattern to the §5Provider§r in front.
chat.appliedenergistics2.PatternRepeaterRouteOutputTrue=Route pattern output.
chat.appliedenergistics2.PatternRepeaterRouteOutputFalse=Ignore pattern output.
chat.appliedenergistics2.WirelessHighlighted=%d §e"%d§e"§r is now highlighted at %d, %d, %d
chat.appliedenergistics2.WirelessInOtherDim=%d §e"%d§e"§r located in dimension: %d and can't be highlighted
chat.appliedenergistics2.WirelessHighlighterPrefix=Wireless
@@ -939,6 +941,8 @@ waila.appliedenergistics2.Disconnected=Disconnected
waila.appliedenergistics2.Singularity=Singularity
waila.appliedenergistics2.Provider=§5Provider§r
waila.appliedenergistics2.Accessor=§5Accessor§r
waila.appliedenergistics2.RouteOutputTrue=§5Route Output§r
waila.appliedenergistics2.RouteOutputFalse=§5Ignore Output§r
waila.appliedenergistics2.Waiting=Waiting:
waila.appliedenergistics2.IOBusSpeed=Speed: %s %s/t