Allow configuring reshuffler access on ME storage buses (#1659)

This commit is contained in:
Worive authored and GitHub committed 2026-10-01 20:49:45 +02:00
1 parent cc6bcd2ab7
commit 2b66781490
6 files changed
+294 -2

No files matched your search

@@ -49,6 +49,7 @@ public class GuiStorageBus extends GuiUpgradeable {
private GuiImgButton partition;
private GuiImgButton clear;
private GuiImgButton extractionMode;
private GuiImgButton reshuffleAccess;
private VirtualMEPhantomSlot[] configSlots;
private final ContainerStorageBus containerStorageBus;
private final IStorageBus storageBus;
@@ -90,6 +91,11 @@ public class GuiStorageBus extends GuiUpgradeable {
this.guiTop + 108,
Settings.ACTIONS,
ActionItems.ORE_FILTER);
this.reshuffleAccess = new GuiImgButton(
this.guiLeft - 18,
this.guiTop + 108,
Settings.RESHUFFLE_ACCESS,
AccessRestriction.READ_WRITE);
this.buttonList.add(
this.priority = new GuiTabButton(
@@ -106,6 +112,16 @@ public class GuiStorageBus extends GuiUpgradeable {
this.buttonList.add(this.partition);
this.buttonList.add(this.clear);
this.buttonList.add(this.oreFilter);
this.buttonList.add(this.reshuffleAccess);
}
@Override
protected void handleButtonVisibility() {
super.handleButtonVisibility();
if (this.reshuffleAccess != null) {
this.reshuffleAccess.yPosition = this.guiTop
+ (this.fuzzyMode.visible || this.oreFilter.visible ? 128 : 108);
}
}
@Override
@@ -147,6 +163,9 @@ public class GuiStorageBus extends GuiUpgradeable {
if (this.partition != null) {
this.partition.set(csb.getPartitionMode());
}
if (this.reshuffleAccess != null) {
this.reshuffleAccess.set(csb.getReshuffleAccess());
}
}
updateSlotVisibility();
@@ -199,6 +218,8 @@ public class GuiStorageBus extends GuiUpgradeable {
} else if (btn == this.storageFilter) {
NetworkHandler.instance
.sendToServer(new PacketConfigButton(this.storageFilter.getSetting(), backwards));
} else if (btn == this.reshuffleAccess) {
NetworkHandler.instance.sendToServer(new PacketConfigButton(Settings.RESHUFFLE_ACCESS, backwards));
}
} catch (final IOException e) {
AELog.debug(e);
@@ -66,6 +66,9 @@ public class ContainerStorageBus extends ContainerUpgradeable implements IVirtua
@GuiSync(9)
public ExtractionMode extractionMode;
@GuiSync(10)
public AccessRestriction reshuffleAccess = AccessRestriction.READ_WRITE;
private final IAEStack<?>[] configClientSlot = new IAEStack[63];
public ContainerStorageBus(final InventoryPlayer ip, final IStorageBus te) {
@@ -150,6 +153,8 @@ public class ContainerStorageBus extends ContainerUpgradeable implements IVirtua
this.setStickyMode((YesNo) this.getUpgradeable().getConfigManager().getSetting(Settings.STICKY_MODE));
this.setExtractionMode(
(ExtractionMode) this.getUpgradeable().getConfigManager().getSetting(Settings.EXTRACTION_MODE));
this.reshuffleAccess = (AccessRestriction) this.getUpgradeable().getConfigManager()
.getSetting(Settings.RESHUFFLE_ACCESS);
final IAEStackInventory config = this.storageBus.getAEInventoryByName(StorageName.CONFIG);
this.updateVirtualSlots(StorageName.CONFIG, config, this.configClientSlot);
@@ -310,6 +315,10 @@ public class ContainerStorageBus extends ContainerUpgradeable implements IVirtua
return this.storageBus.getStackType();
}
public AccessRestriction getReshuffleAccess() {
return this.reshuffleAccess;
}
public IAEStackInventory getConfig() {
return this.storageBus.getAEInventoryByName(StorageName.CONFIG);
}
@@ -28,7 +28,10 @@ import com.gtnewhorizons.horizonqa.api.annotation.GameTestHolder;
import appeng.api.AEApi;
import appeng.api.config.AccessRestriction;
import appeng.api.config.Actionable;
import appeng.api.config.Settings;
import appeng.api.networking.security.MachineSource;
import appeng.api.networking.security.ReshuffleActionSource;
import appeng.api.parts.PartItemStack;
import appeng.api.storage.IMEInventoryHandler;
import appeng.api.storage.StorageName;
@@ -62,6 +65,117 @@ public class StorageBusTests {
}).thenSucceed();
}
@GameTest(template = "storage_bus", timeoutTicks = 100)
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void reshufflerAccessControlsExternalChest(GameTestHelper helper) {
TileController controller = getController(helper);
PartStorageBus storageBus = getStorageBus(helper);
helper.setSlot(EXTERNAL_CHEST_LABEL, 0, new ItemStack(Blocks.cobblestone, 64));
helper.startSequence().thenWaitUntil("wait for storage bus inventory", 60, () -> {
assertActive(helper, controller.getProxy(), "Controller grid proxy should become active");
assertActive(helper, storageBus, "Storage bus should receive a channel");
helper.assertFalse(storageBus.getCellArray(ITEM_STACK_TYPE).isEmpty(), "Storage bus inventory is missing");
}).thenExecute("verify reshuffler access modes", () -> {
IMEInventoryHandler inventory = storageBus.getCellArray(ITEM_STACK_TYPE).get(0);
ReshuffleActionSource reshuffleSource = new ReshuffleActionSource(controller);
MachineSource ordinarySource = new MachineSource(controller);
for (AccessRestriction access : AccessRestriction.values()) {
storageBus.getConfigManager().putSetting(Settings.RESHUFFLE_ACCESS, access);
helper.assertEquals(access, inventory.getReshuffleAccess(), "Storage bus should report its access");
helper.assertEquals(
access.hasPermission(AccessRestriction.READ),
inventory.extractItems(itemStack(Blocks.cobblestone, 1), Actionable.SIMULATE, reshuffleSource)
!= null,
access + " should control reshuffler extraction");
helper.assertEquals(
access.hasPermission(AccessRestriction.WRITE),
inventory.injectItems(itemStack(Blocks.dirt, 1), Actionable.SIMULATE, reshuffleSource) == null,
access + " should control reshuffler insertion");
helper.assertNotNull(
inventory.extractItems(itemStack(Blocks.cobblestone, 1), Actionable.SIMULATE, ordinarySource),
"Ordinary extraction should remain allowed");
helper.assertNull(
inventory.injectItems(itemStack(Blocks.dirt, 1), Actionable.SIMULATE, ordinarySource),
"Ordinary insertion should remain allowed");
}
storageBus.getConfigManager().putSetting(Settings.RESHUFFLE_ACCESS, AccessRestriction.NO_ACCESS);
helper.assertEquals(
AccessRestriction.NO_ACCESS,
reloadStorageBus(storageBus).getReshuffleAccess(),
"Storage bus should retain reshuffler access after reload");
}).thenSucceed();
}
@GameTest(template = "storage_bus", timeoutTicks = 240)
@SuppressWarnings({ "rawtypes", "unchecked" })
public static void normalAccessAlsoRestrictsReshuffler(GameTestHelper helper) {
TileController controller = getController(helper);
PartStorageBus storageBus = getStorageBus(helper);
storageBus.getConfigManager().putSetting(Settings.ACCESS, AccessRestriction.WRITE);
helper.setSlot(EXTERNAL_CHEST_LABEL, 0, new ItemStack(Blocks.cobblestone, 64));
helper.startSequence().thenWaitUntil("wait for input-only storage bus", 80, () -> {
assertActive(helper, controller.getProxy(), "Controller grid proxy should become active");
assertActive(helper, storageBus, "Storage bus should receive a channel");
IMEInventoryHandler inventory = storageBus.getInternalHandler();
helper.assertNotNull(inventory, "Storage bus inventory is missing");
helper.assertEquals(AccessRestriction.WRITE, inventory.getAccess(), "Bus should be input-only");
}).thenExecute("verify input-only reshuffler access", () -> {
IMEInventoryHandler inventory = storageBus.getInternalHandler();
ReshuffleActionSource source = new ReshuffleActionSource(controller);
MachineSource ordinarySource = new MachineSource(controller);
helper.assertEquals(
AccessRestriction.WRITE,
inventory.getReshuffleAccess(),
"Input-only bus should not be an extraction source, including during subnet traversal");
helper.assertNull(
inventory.extractItems(itemStack(Blocks.cobblestone, 1), Actionable.SIMULATE, source),
"Reshuffler should not extract through an input-only bus");
helper.assertNull(
inventory.injectItems(itemStack(Blocks.dirt, 1), Actionable.SIMULATE, source),
"Reshuffler should insert through an input-only bus");
storageBus.getConfigManager().putSetting(Settings.RESHUFFLE_ACCESS, AccessRestriction.READ);
helper.assertEquals(
AccessRestriction.NO_ACCESS,
inventory.getReshuffleAccess(),
"Input-only bus plus extract-only reshuffler access should deny both directions");
helper.assertNull(
inventory.extractItems(itemStack(Blocks.cobblestone, 1), Actionable.SIMULATE, source),
"Conflicting settings should deny reshuffler extraction");
helper.assertNotNull(
inventory.injectItems(itemStack(Blocks.dirt, 1), Actionable.SIMULATE, source),
"Conflicting settings should deny reshuffler insertion");
helper.assertNull(
inventory.injectItems(itemStack(Blocks.dirt, 1), Actionable.SIMULATE, ordinarySource),
"Conflicting reshuffler setting should not prevent ordinary insertion");
storageBus.getConfigManager().putSetting(Settings.RESHUFFLE_ACCESS, AccessRestriction.READ_WRITE);
storageBus.getConfigManager().putSetting(Settings.ACCESS, AccessRestriction.READ);
}).thenWaitUntil("wait for output-only storage bus", 80, () -> {
IMEInventoryHandler inventory = storageBus.getInternalHandler();
helper.assertNotNull(inventory, "Storage bus inventory is missing");
helper.assertEquals(AccessRestriction.READ, inventory.getAccess(), "Bus should be output-only");
}).thenExecute("verify output-only reshuffler access", () -> {
IMEInventoryHandler inventory = storageBus.getInternalHandler();
ReshuffleActionSource source = new ReshuffleActionSource(controller);
helper.assertEquals(
AccessRestriction.READ,
inventory.getReshuffleAccess(),
"Output-only bus should not be an injection target");
helper.assertNotNull(
inventory.extractItems(itemStack(Blocks.cobblestone, 1), Actionable.SIMULATE, source),
"Reshuffler should extract through an output-only bus");
helper.assertNotNull(
inventory.injectItems(itemStack(Blocks.dirt, 1), Actionable.SIMULATE, source),
"Reshuffler should not insert through an output-only bus");
}).thenSucceed();
}
// Reflects external chest mutations after the storage bus monitor refreshes.
@GameTest(template = "storage_bus", timeoutTicks = 140)
public static void storageBusReflectsExternalMutation(GameTestHelper helper) {
@@ -0,0 +1,131 @@
package appeng.gametests.compatibility.appliedenergistics2_ae2fc;
import static appeng.gametests.AEGameTestHelpers.assertActive;
import static appeng.gametests.AEGameTestHelpers.fluidStack;
import static appeng.gametests.AEGameTestHelpers.part;
import static appeng.util.item.AEFluidStackType.FLUID_STACK_TYPE;
import net.minecraft.item.ItemStack;
import net.minecraftforge.common.util.ForgeDirection;
import net.minecraftforge.fluids.Fluid;
import net.minecraftforge.fluids.FluidRegistry;
import net.minecraftforge.fluids.FluidStack;
import com.glodblock.github.common.parts.PartFluidStorageBus;
import com.glodblock.github.common.tile.TileCertusQuartzTank;
import com.glodblock.github.loader.ItemAndBlockHolder;
import com.gtnewhorizons.horizonqa.api.GameTestHelper;
import com.gtnewhorizons.horizonqa.api.annotation.GameTest;
import com.gtnewhorizons.horizonqa.api.annotation.GameTestHolder;
import appeng.api.config.AccessRestriction;
import appeng.api.config.Actionable;
import appeng.api.config.Settings;
import appeng.api.networking.security.MachineSource;
import appeng.api.networking.security.ReshuffleActionSource;
import appeng.api.parts.IPart;
import appeng.api.parts.IPartHost;
import appeng.api.storage.IMEInventoryHandler;
import appeng.api.storage.data.IAEFluidStack;
import appeng.core.AppEng;
import appeng.parts.misc.PartStorageBus;
import appeng.tile.networking.TileController;
@GameTestHolder(value = AppEng.MOD_ID, requiredMods = "ae2fc")
public class FluidStorageBusReshuffleTests {
private static final String CONTROLLER_LABEL = "controller";
private static final String STORAGE_BUS_LABEL = "storage_bus";
private static final String EXTERNAL_TANK_LABEL = "external_chest";
@GameTest(template = "storage_bus", timeoutTicks = 180)
public static void fluidBusRespectsReshufflerAndNormalAccess(GameTestHelper helper) {
TileController controller = helper.assertTileEntityPresent(TileController.class, CONTROLLER_LABEL);
Fluid water = FluidRegistry.WATER;
helper.assertNotNull(water, "Water fluid should be registered");
helper.setBlock(EXTERNAL_TANK_LABEL, ItemAndBlockHolder.CERTUS_QUARTZ_TANK);
TileCertusQuartzTank tank = helper.assertTileEntityPresent(TileCertusQuartzTank.class, EXTERNAL_TANK_LABEL);
helper.assertEquals(
8_000,
tank.fill(ForgeDirection.UNKNOWN, new FluidStack(water, 8_000), true),
"External tank should start with 8,000 mB of water");
PartStorageBus oldBus = part(helper, STORAGE_BUS_LABEL, PartStorageBus.class);
IPartHost host = oldBus.getHost();
ForgeDirection side = oldBus.getSide();
host.removePart(side, true);
ItemStack fluidBusItem = new ItemStack(ItemAndBlockHolder.FLUID_STORAGE_BUS);
helper.assertEquals(side, host.addPart(fluidBusItem, side, null), "Fluid storage bus should be placed");
IPart placedPart = host.getPart(side);
helper.assertTrue(placedPart instanceof PartFluidStorageBus, "Replacement should be an AE2 FC fluid bus");
PartFluidStorageBus bus = (PartFluidStorageBus) placedPart;
helper.startSequence().thenWaitUntil("wait for fluid storage bus inventory", 80, () -> {
assertActive(helper, controller.getProxy(), "Controller grid proxy should become active");
assertActive(helper, bus, "Fluid storage bus should receive a channel");
helper.assertEquals(1, bus.getCellArray(FLUID_STACK_TYPE).size(), "Fluid inventory should be exposed");
helper.assertNotNull(
fluidInventory(bus)
.extractItems(fluidStack(water, 1_000), Actionable.SIMULATE, new MachineSource(controller)),
"Fluid bus should expose the external tank's water");
}).thenExecute("verify real fluid transfers and reshuffler access modes", () -> {
IMEInventoryHandler<IAEFluidStack> inventory = fluidInventory(bus);
ReshuffleActionSource reshuffleSource = new ReshuffleActionSource(controller);
MachineSource ordinarySource = new MachineSource(controller);
IAEFluidStack extracted = inventory
.extractItems(fluidStack(water, 1_000), Actionable.MODULATE, reshuffleSource);
helper.assertNotNull(extracted, "Reshuffler should extract water from the external tank");
helper.assertEquals(1_000L, extracted.getStackSize(), "Reshuffler should extract 1,000 mB");
helper.assertEquals(7_000, tank.tank.getFluidAmount(), "External tank should lose the extracted water");
helper.assertNull(
inventory.injectItems(extracted, Actionable.MODULATE, reshuffleSource),
"Reshuffler should return water to the external tank");
helper.assertEquals(8_000, tank.tank.getFluidAmount(), "External tank should regain the water");
for (AccessRestriction access : AccessRestriction.values()) {
bus.getConfigManager().putSetting(Settings.RESHUFFLE_ACCESS, access);
helper.assertEquals(access, inventory.getReshuffleAccess(), "Fluid bus should report its access");
helper.assertEquals(
access.hasPermission(AccessRestriction.READ),
inventory.extractItems(fluidStack(water, 1_000), Actionable.SIMULATE, reshuffleSource) != null,
access + " should control reshuffler fluid extraction");
helper.assertEquals(
access.hasPermission(AccessRestriction.WRITE),
inventory.injectItems(fluidStack(water, 1_000), Actionable.SIMULATE, reshuffleSource) == null,
access + " should control reshuffler fluid insertion");
helper.assertNotNull(
inventory.extractItems(fluidStack(water, 1_000), Actionable.SIMULATE, ordinarySource),
"Ordinary fluid extraction should remain allowed");
helper.assertNull(
inventory.injectItems(fluidStack(water, 1_000), Actionable.SIMULATE, ordinarySource),
"Ordinary fluid insertion should remain allowed");
}
bus.getConfigManager().putSetting(Settings.ACCESS, AccessRestriction.WRITE);
bus.getConfigManager().putSetting(Settings.RESHUFFLE_ACCESS, AccessRestriction.READ);
}).thenWaitUntil("wait for contradictory fluid bus access settings", 80, () -> {
IMEInventoryHandler<IAEFluidStack> inventory = fluidInventory(bus);
helper.assertEquals(AccessRestriction.WRITE, inventory.getAccess(), "Bus should be input-only");
}).thenExecute("verify conflicting settings deny reshuffler fluid transfers", () -> {
IMEInventoryHandler<IAEFluidStack> inventory = fluidInventory(bus);
ReshuffleActionSource reshuffleSource = new ReshuffleActionSource(controller);
helper.assertEquals(
AccessRestriction.NO_ACCESS,
inventory.getReshuffleAccess(),
"Input-only bus plus extract-only reshuffler setting should deny both directions");
helper.assertNull(
inventory.extractItems(fluidStack(water, 1_000), Actionable.SIMULATE, reshuffleSource),
"Conflicting settings should deny reshuffler fluid extraction");
helper.assertNotNull(
inventory.injectItems(fluidStack(water, 1_000), Actionable.SIMULATE, reshuffleSource),
"Conflicting settings should deny reshuffler fluid insertion");
}).thenSucceed();
}
@SuppressWarnings("unchecked")
private static IMEInventoryHandler<IAEFluidStack> fluidInventory(PartFluidStorageBus bus) {
return (IMEInventoryHandler<IAEFluidStack>) bus.getCellArray(FLUID_STACK_TYPE).get(0);
}
}
@@ -2,7 +2,9 @@ package appeng.me.storage;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.function.Supplier;
import appeng.api.config.AccessRestriction;
import appeng.api.storage.IMEInventory;
import appeng.api.storage.IMENetworkInventory;
import appeng.api.storage.data.IAEStack;
@@ -14,8 +16,18 @@ import appeng.util.item.PrioritizedNetworkItemList;
public class StorageBusInventoryHandler<T extends IAEStack<T>> extends MEInventoryHandler<T> {
public StorageBusInventoryHandler(IMEInventory<T> i, IAEStackType<T> type) {
private final Supplier<AccessRestriction> reshuffleAccess;
public StorageBusInventoryHandler(IMEInventory<T> i, IAEStackType<T> type,
Supplier<AccessRestriction> reshuffleAccess) {
super(i, type);
this.reshuffleAccess = reshuffleAccess;
}
@Override
public AccessRestriction getReshuffleAccess() {
return this.getAccess().restrictPermissions(this.reshuffleAccess.get())
.restrictPermissions(super.getReshuffleAccess());
}
@Override
@@ -139,6 +139,7 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
public PartStorageBus(final ItemStack is) {
super(is);
this.getConfigManager().registerSetting(Settings.ACCESS, AccessRestriction.READ_WRITE);
this.getConfigManager().registerSetting(Settings.RESHUFFLE_ACCESS, AccessRestriction.READ_WRITE);
this.getConfigManager().registerSetting(Settings.EXTRACTION_MODE, ExtractionMode.LOOSE);
this.getConfigManager().registerSetting(Settings.FUZZY_MODE, FuzzyMode.IGNORE_ALL);
this.getConfigManager().registerSetting(Settings.STORAGE_FILTER, StorageFilter.EXTRACTABLE_ONLY);
@@ -664,7 +665,7 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
if (inv != null) {
this.checkInterfaceVsStorageBus(target, this.getSide().getOpposite());
this.handler = new StorageBusInventoryHandler<>(inv, this.getStackType());
this.handler = new StorageBusInventoryHandler<>(inv, this.getStackType(), this::getReshuffleAccess);
AccessRestriction currentAccess = (AccessRestriction) this.getConfigManager()
.getSetting(Settings.ACCESS);
@@ -781,6 +782,10 @@ public class PartStorageBus extends PartUpgradeable implements IStorageBus {
return Collections.emptyList();
}
public AccessRestriction getReshuffleAccess() {
return (AccessRestriction) this.getConfigManager().getSetting(Settings.RESHUFFLE_ACCESS);
}
@Override
public int getPriority() {
return this.priority;