mirror of
https://github.com/SquidDev-CC/CC-Tweaked
synced 2026-10-23 16:02:30 +00:00
Add cross-platform abstraction for inventories
- Replace our existing ContainerTransfer class with a more general ItemContainer. This supports all the functionality we need for our inventory peripheral. - Change AbstractInventoryMethod to use ContainerTransfer. This reduces the duplicated code between the two InventoryMethod implementations (well, somewhat) — they only now have to handle extracting ContainerTransfers.
This commit is contained in:
+87
-10
@@ -5,16 +5,22 @@
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.ComputerCraftAPI;
|
||||
import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.GenericPeripheral;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.api.peripheral.PeripheralType;
|
||||
import dan200.computercraft.shared.platform.ItemContainer;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
|
||||
|
||||
/**
|
||||
* Methods for interacting with inventories.
|
||||
*
|
||||
@@ -33,6 +39,22 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
return ComputerCraftAPI.MOD_ID + ":inventory";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an {@link ItemContainer} for the generic inventory type.
|
||||
*
|
||||
* @param inventory The inventory to get our {@link ItemContainer} from.
|
||||
* @return The resulting {@link ItemContainer}.
|
||||
*/
|
||||
protected abstract ItemContainer getContainer(T inventory);
|
||||
|
||||
/**
|
||||
* Try to extract a {@link ItemContainer} from a peripheral.
|
||||
*
|
||||
* @param peripheral The peripheral to get our {@link ItemContainer} from.
|
||||
* @return The resulting {@link ItemContainer}, or {@code null} if this peripheral is not a container.
|
||||
*/
|
||||
protected abstract @Nullable ItemContainer getContainer(IPeripheral peripheral);
|
||||
|
||||
/**
|
||||
* Get the size of this inventory.
|
||||
*
|
||||
@@ -40,7 +62,9 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
* @return The number of slots in this inventory.
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int size(T inventory);
|
||||
public final int size(T inventory) {
|
||||
return getContainer(inventory).size();
|
||||
}
|
||||
|
||||
/**
|
||||
* List all items in this inventory. This returns a table, with an entry for each slot.
|
||||
@@ -67,7 +91,17 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
* @cc.see item_details
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<Integer, Map<String, ?>> list(T inventory);
|
||||
public final Map<Integer, Map<String, ?>> list(T inventory) {
|
||||
var container = getContainer(inventory);
|
||||
Map<Integer, Map<String, ?>> result = new HashMap<>();
|
||||
var i = 0;
|
||||
for (var slots = container.contents(); slots.hasNext(); i++) {
|
||||
var stack = slots.next();
|
||||
if (!stack.isEmpty()) result.put(i + 1, VanillaDetailRegistries.ITEM_STACK.getBasicDetails(stack));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [detailed information][`item_details`] about an item.
|
||||
@@ -94,7 +128,13 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
*/
|
||||
@Nullable
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract Map<?, ?> getItemDetail(T inventory, int slot) throws LuaException;
|
||||
public final Map<?, ?> getItemDetail(T inventory, int slot) throws LuaException {
|
||||
var container = getContainer(inventory);
|
||||
assertBetween(slot, 1, container.size(), "Slot out of range (%s)");
|
||||
|
||||
var stack = container.getItem(slot - 1);
|
||||
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum number of items which can be stored in this slot.
|
||||
@@ -118,13 +158,17 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
* @cc.since 1.96.0
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract long getItemLimit(T inventory, int slot) throws LuaException;
|
||||
public final long getItemLimit(T inventory, int slot) throws LuaException {
|
||||
var container = getContainer(inventory);
|
||||
assertBetween(slot, 1, container.size(), "Slot out of range (%s)");
|
||||
return container.getCapacity(slot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push items from one inventory to another connected one.
|
||||
* <p>
|
||||
* This allows you to push an item in an inventory to another inventory <em>on the same wired network</em>. Both
|
||||
* inventories must attached to wired modems which are connected via a cable.
|
||||
* inventories must be attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param from Inventory to move items from.
|
||||
* @param computer The current computer.
|
||||
@@ -146,15 +190,24 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pushItems(
|
||||
public final int pushItems(
|
||||
T from, IComputerAccess computer, String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException;
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
var location = computer.getAvailablePeripheral(toName);
|
||||
if (location == null) throw new LuaException("Target '" + toName + "' does not exist");
|
||||
|
||||
var to = getContainer(location);
|
||||
if (to == null) throw new LuaException("Target '" + toName + "' is not an inventory");
|
||||
|
||||
return move(getContainer(from), fromSlot, to, toSlot, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull items from a connected inventory into this one.
|
||||
* <p>
|
||||
* This allows you to transfer items between inventories <em>on the same wired network</em>. Both this and the source
|
||||
* inventory must attached to wired modems which are connected via a cable.
|
||||
* inventory must be attached to wired modems which are connected via a cable.
|
||||
*
|
||||
* @param to Inventory to move items to.
|
||||
* @param computer The current computer.
|
||||
@@ -176,7 +229,31 @@ public abstract class AbstractInventoryMethods<T> implements GenericPeripheral {
|
||||
* }</pre>
|
||||
*/
|
||||
@LuaFunction(mainThread = true)
|
||||
public abstract int pullItems(
|
||||
public final int pullItems(
|
||||
T to, IComputerAccess computer, String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException;
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
var location = computer.getAvailablePeripheral(fromName);
|
||||
if (location == null) throw new LuaException("Source '" + fromName + "' does not exist");
|
||||
|
||||
var from = getContainer(location);
|
||||
if (from == null) throw new LuaException("Source '" + fromName + "' is not an inventory");
|
||||
|
||||
return move(from, fromSlot, getContainer(to), toSlot, limit);
|
||||
}
|
||||
|
||||
public static int move(ItemContainer from, int fromSlot, ItemContainer to, Optional<Integer> toSlot, Optional<Integer> limit) throws LuaException {
|
||||
assertBetween(fromSlot, 1, from.size(), "From slot out of range (%s)");
|
||||
|
||||
if (toSlot.isPresent()) {
|
||||
assertBetween(toSlot.get(), 1, to.size(), "To slot out of range (%s)");
|
||||
to = to.singleSlot(toSlot.get() - 1);
|
||||
if (to == null) throw new LuaException("Cannot specify toSlot for this inventory");
|
||||
}
|
||||
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
if (actualLimit <= 0) return 0;
|
||||
|
||||
return Math.max(0, from.moveTo(fromSlot - 1, to, actualLimit));
|
||||
}
|
||||
}
|
||||
|
||||
-47
@@ -1,47 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import net.minecraft.world.Container;
|
||||
|
||||
/**
|
||||
* A quasi-{@link Container}, which just supports transferring items.
|
||||
*/
|
||||
public interface ContainerTransfer {
|
||||
int NO_ITEMS = -1;
|
||||
int NO_SPACE = -2;
|
||||
|
||||
/**
|
||||
* Push an item from this container to another.
|
||||
*
|
||||
* @param destination The container to push to.
|
||||
* @param maxAmount The maximum number of items to move.
|
||||
* @return The number of items which were transferred, or one of {@link #NO_ITEMS} or {@link #NO_SPACE}. This will
|
||||
* <em>NEVER</em> return 0.
|
||||
*/
|
||||
int moveTo(ContainerTransfer destination, int maxAmount);
|
||||
|
||||
/**
|
||||
* A {@link ContainerTransfer} which also has slots.
|
||||
*/
|
||||
interface Slotted extends ContainerTransfer {
|
||||
/**
|
||||
* Create a new {@link ContainerTransfer} which rotates the inventory, so that inserts start at {@code offset}
|
||||
* instead.
|
||||
*
|
||||
* @param offset The slot offset
|
||||
* @return The new container transfer.
|
||||
*/
|
||||
ContainerTransfer rotate(int offset);
|
||||
|
||||
/**
|
||||
* Create a new {@link ContainerTransfer} which can view a single slot of this container.
|
||||
*
|
||||
* @param slot The slot we can view.
|
||||
* @return The new container transfer.
|
||||
*/
|
||||
ContainerTransfer singleSlot(int slot);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* An abstraction over containers of items, such as {@link Container} and mod-loader specific inventory APIs.
|
||||
*/
|
||||
public interface ItemContainer {
|
||||
int NO_ITEMS = -1;
|
||||
int NO_SPACE = -2;
|
||||
|
||||
int size();
|
||||
|
||||
Iterator<ItemStack> contents();
|
||||
|
||||
ItemStack getItem(int slot);
|
||||
|
||||
int getCapacity(int slot);
|
||||
|
||||
@Nullable ItemContainer singleSlot(int slot);
|
||||
|
||||
/**
|
||||
* Push an item from this container to another.
|
||||
*
|
||||
* @param destination The container to push to.
|
||||
* @param maxAmount The maximum number of items to move.
|
||||
* @return The number of items which were transferred, or one of {@link #NO_ITEMS} or {@link #NO_SPACE}. This will
|
||||
* <em>NEVER</em> return 0.
|
||||
*/
|
||||
int moveTo(ItemContainer destination, int maxAmount);
|
||||
|
||||
/**
|
||||
* Push an item from this container to another.
|
||||
*
|
||||
* @param fromSlot The slot to move items from.
|
||||
* @param destination The container to push to.
|
||||
* @param maxAmount The maximum number of items to move.
|
||||
* @return The number of items which were transferred, or one of {@link #NO_ITEMS} or {@link #NO_SPACE}. This will
|
||||
* <em>NEVER</em> return 0.
|
||||
*/
|
||||
int moveTo(int fromSlot, ItemContainer destination, int maxAmount);
|
||||
}
|
||||
+14
-4
@@ -224,15 +224,25 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
||||
boolean hasWiredElementIn(Level level, BlockPos pos, Direction direction);
|
||||
|
||||
/**
|
||||
* Wrap a vanilla Minecraft {@link Container} into a {@link ContainerTransfer}.
|
||||
* Wrap a vanilla Minecraft {@link Container} into a {@link ItemContainer}.
|
||||
*
|
||||
* @param container The container to wrap.
|
||||
* @return The container transfer.
|
||||
*/
|
||||
ContainerTransfer.Slotted wrapContainer(Container container);
|
||||
ItemContainer wrapContainer(Container container);
|
||||
|
||||
/**
|
||||
* Get access to a {@link ContainerTransfer} for a given position. This should look up blocks, then fall back to
|
||||
* Wrap a vanilla Minecraft {@link Container} into a {@link ItemContainer}, "rotating" the container so that every
|
||||
* slot {@code i} maps to {@code (i + offset) % size} of the original container.
|
||||
*
|
||||
* @param container The container to wrap.
|
||||
* @param offset The offset into the container.
|
||||
* @return The container transfer.
|
||||
*/
|
||||
ItemContainer wrapContainerRotated(Container container, int offset);
|
||||
|
||||
/**
|
||||
* Get access to a {@link ItemContainer} for a given position. This should look up blocks, then fall back to
|
||||
* {@link InventoryUtil#getEntityContainer(ServerLevel, BlockPos, Direction)}
|
||||
*
|
||||
* @param level The current level.
|
||||
@@ -242,7 +252,7 @@ public interface PlatformHelper extends dan200.computercraft.impl.PlatformHelper
|
||||
* @return The container, or {@code null} if none exists.
|
||||
*/
|
||||
@Nullable
|
||||
ContainerTransfer getContainer(ServerLevel level, BlockPos pos, Direction side);
|
||||
ItemContainer getContainer(ServerLevel level, BlockPos pos, Direction side);
|
||||
|
||||
/**
|
||||
* Get the {@link RecipeIngredients} for this loader.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package dan200.computercraft.shared.turtle;
|
||||
|
||||
import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.shared.platform.ContainerTransfer;
|
||||
import dan200.computercraft.shared.platform.ItemContainer;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.util.DropConsumer;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
@@ -15,6 +15,16 @@ import net.minecraft.world.item.ItemStack;
|
||||
import java.util.function.Function;
|
||||
|
||||
public class TurtleUtil {
|
||||
/**
|
||||
* Get a view of the turtle's inventory. This should be used when transferring items from the turtle.
|
||||
*
|
||||
* @param turtle The turtle to transfer items into.
|
||||
* @return The container transfer
|
||||
*/
|
||||
public static ItemContainer getInventory(ITurtleAccess turtle) {
|
||||
return PlatformHelper.get().wrapContainer(turtle.getInventory());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a view of the turtle's inventory starting at the currently selected slot. This should be used when
|
||||
* transferring items in to the turtle.
|
||||
@@ -22,18 +32,8 @@ public class TurtleUtil {
|
||||
* @param turtle The turtle to transfer items into.
|
||||
* @return The container transfer
|
||||
*/
|
||||
public static ContainerTransfer getOffsetInventory(ITurtleAccess turtle) {
|
||||
return PlatformHelper.get().wrapContainer(turtle.getInventory()).rotate(turtle.getSelectedSlot());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a view of the turtle's currently selected slot. This should be used when transferring items from the turtle.
|
||||
*
|
||||
* @param turtle The turtle to transfer items from.
|
||||
* @return The container transfer.
|
||||
*/
|
||||
public static ContainerTransfer getSelectedSlot(ITurtleAccess turtle) {
|
||||
return PlatformHelper.get().wrapContainer(turtle.getInventory()).singleSlot(turtle.getSelectedSlot());
|
||||
public static ItemContainer getOffsetInventory(ITurtleAccess turtle) {
|
||||
return PlatformHelper.get().wrapContainerRotated(turtle.getInventory(), turtle.getSelectedSlot());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-7
@@ -8,7 +8,7 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.TurtleAnimation;
|
||||
import dan200.computercraft.api.turtle.TurtleCommand;
|
||||
import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.shared.platform.ContainerTransfer;
|
||||
import dan200.computercraft.shared.platform.ItemContainer;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleUtil;
|
||||
import dan200.computercraft.shared.util.WorldUtil;
|
||||
@@ -35,8 +35,6 @@ public class TurtleDropCommand implements TurtleCommand {
|
||||
// Get world direction from direction
|
||||
var direction = this.direction.toWorldDir(turtle);
|
||||
|
||||
var source = TurtleUtil.getSelectedSlot(turtle);
|
||||
|
||||
// Get inventory for thing in front
|
||||
var world = turtle.getLevel();
|
||||
var oldPosition = turtle.getPosition();
|
||||
@@ -47,11 +45,11 @@ public class TurtleDropCommand implements TurtleCommand {
|
||||
|
||||
int transferred;
|
||||
if (inventory != null) {
|
||||
transferred = source.moveTo(inventory, quantity);
|
||||
transferred = TurtleUtil.getInventory(turtle).moveTo(turtle.getSelectedSlot(), inventory, quantity);
|
||||
} else {
|
||||
var stack = turtle.getInventory().removeItem(turtle.getSelectedSlot(), quantity);
|
||||
if (stack.isEmpty()) {
|
||||
transferred = ContainerTransfer.NO_ITEMS;
|
||||
transferred = ItemContainer.NO_ITEMS;
|
||||
} else {
|
||||
// Drop the item into the world
|
||||
turtle.getInventory().setChanged();
|
||||
@@ -63,8 +61,8 @@ public class TurtleDropCommand implements TurtleCommand {
|
||||
}
|
||||
|
||||
return switch (transferred) {
|
||||
case ContainerTransfer.NO_SPACE -> TurtleCommandResult.failure("No space for items");
|
||||
case ContainerTransfer.NO_ITEMS -> TurtleCommandResult.failure("No items to drop");
|
||||
case ItemContainer.NO_SPACE -> TurtleCommandResult.failure("No space for items");
|
||||
case ItemContainer.NO_ITEMS -> TurtleCommandResult.failure("No items to drop");
|
||||
default -> {
|
||||
turtle.playAnimation(TurtleAnimation.WAIT);
|
||||
yield TurtleCommandResult.success();
|
||||
|
||||
+3
-3
@@ -8,7 +8,7 @@ import dan200.computercraft.api.turtle.ITurtleAccess;
|
||||
import dan200.computercraft.api.turtle.TurtleAnimation;
|
||||
import dan200.computercraft.api.turtle.TurtleCommand;
|
||||
import dan200.computercraft.api.turtle.TurtleCommandResult;
|
||||
import dan200.computercraft.shared.platform.ContainerTransfer;
|
||||
import dan200.computercraft.shared.platform.ItemContainer;
|
||||
import dan200.computercraft.shared.platform.PlatformHelper;
|
||||
import dan200.computercraft.shared.turtle.TurtleUtil;
|
||||
import dan200.computercraft.shared.util.InventoryUtil;
|
||||
@@ -51,8 +51,8 @@ public class TurtleSuckCommand implements TurtleCommand {
|
||||
// Take from inventory of thing in front
|
||||
var transferred = inventory.moveTo(TurtleUtil.getOffsetInventory(turtle), quantity);
|
||||
return switch (transferred) {
|
||||
case ContainerTransfer.NO_SPACE -> TurtleCommandResult.failure("No space for items");
|
||||
case ContainerTransfer.NO_ITEMS -> TurtleCommandResult.failure("No items to take");
|
||||
case ItemContainer.NO_SPACE -> TurtleCommandResult.failure("No space for items");
|
||||
case ItemContainer.NO_ITEMS -> TurtleCommandResult.failure("No items to take");
|
||||
default -> {
|
||||
turtle.playAnimation(TurtleAnimation.WAIT);
|
||||
yield TurtleCommandResult.success();
|
||||
|
||||
@@ -235,13 +235,18 @@ public class TestPlatformHelper extends AbstractComputerCraftAPI implements Plat
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContainerTransfer.Slotted wrapContainer(Container container) {
|
||||
public ItemContainer wrapContainer(Container container) {
|
||||
throw new UnsupportedOperationException("Cannot wrap container");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer wrapContainerRotated(Container container, int offset) {
|
||||
throw new UnsupportedOperationException("Cannot wrap container");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ContainerTransfer getContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
public ItemContainer getContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
throw new UnsupportedOperationException("Cannot interact with the world inside tests");
|
||||
}
|
||||
|
||||
|
||||
+14
-10
@@ -4,7 +4,7 @@
|
||||
|
||||
package dan200.computercraft.test.shared.platform;
|
||||
|
||||
import dan200.computercraft.shared.platform.ContainerTransfer;
|
||||
import dan200.computercraft.shared.platform.ItemContainer;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.SimpleContainer;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
@@ -22,18 +22,22 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
* Test Interface defining the behaviour of a {@link ContainerTransfer} implementation.
|
||||
* Test Interface defining the behaviour of a {@link ItemContainer} implementation.
|
||||
*/
|
||||
public interface ContainerTransferContract {
|
||||
ContainerTransfer.Slotted wrap(Container container);
|
||||
public interface ItemContainerContract {
|
||||
ItemContainer wrap(Container container);
|
||||
|
||||
ItemContainer wrapSlot(Container container, int slot);
|
||||
|
||||
ItemContainer wrapRotated(Container container, int offset);
|
||||
|
||||
@Test
|
||||
default void testMoveSameInventorySlot() {
|
||||
var inv = new SimpleContainer(4);
|
||||
inv.setItem(0, new ItemStack(Items.DIRT, 64));
|
||||
|
||||
var move = wrap(inv).singleSlot(0).moveTo(wrap(inv).singleSlot(0), 64);
|
||||
assertEquals(ContainerTransfer.NO_SPACE, move);
|
||||
var move = wrap(inv).moveTo(0, wrapSlot(inv, 0), 64);
|
||||
assertEquals(ItemContainer.NO_SPACE, move);
|
||||
|
||||
assertThat(inv.getItem(0), isStack(Items.DIRT, 64));
|
||||
|
||||
@@ -45,7 +49,7 @@ public interface ContainerTransferContract {
|
||||
var inv = new SimpleContainer(4);
|
||||
inv.setItem(0, new ItemStack(Items.DIRT, 64));
|
||||
|
||||
var move = wrap(inv).singleSlot(0).moveTo(wrap(inv).singleSlot(1), 64);
|
||||
var move = wrap(inv).moveTo(0, wrapSlot(inv, 1), 64);
|
||||
assertEquals(64, move);
|
||||
|
||||
assertThat(inv.getItem(0), isStack(ItemStack.EMPTY));
|
||||
@@ -106,7 +110,7 @@ public interface ContainerTransferContract {
|
||||
source.setItem(0, new ItemStack(Items.DIRT, 64));
|
||||
source.setItem(1, new ItemStack(Items.DIRT, 64));
|
||||
|
||||
var move = wrap(source).moveTo(wrap(destination).rotate(offset), 17);
|
||||
var move = wrap(source).moveTo(wrapRotated(destination, offset), 17);
|
||||
assertEquals(17, move);
|
||||
|
||||
assertThat("Source stack in slot 0", source.getItem(0), isStack(Items.DIRT, 47));
|
||||
@@ -151,7 +155,7 @@ public interface ContainerTransferContract {
|
||||
source.setItem(0, new ItemStack(Items.DIRT, 64));
|
||||
|
||||
var move = wrap(source).moveTo(wrap(destination), 64);
|
||||
assertEquals(ContainerTransfer.NO_SPACE, move);
|
||||
assertEquals(ItemContainer.NO_SPACE, move);
|
||||
|
||||
assertThat(source.getItem(0), isStack(Items.DIRT, 64));
|
||||
assertThat(destination.getItem(0), isStack(ItemStack.EMPTY));
|
||||
@@ -168,7 +172,7 @@ public interface ContainerTransferContract {
|
||||
destination.setItem(0, new ItemStack(Items.DIRT));
|
||||
for (var slot = 4; slot < 9; slot++) destination.setItem(slot, new ItemStack(Items.DIRT));
|
||||
|
||||
var move = wrap(source).moveTo(wrap(destination).rotate(4), 64);
|
||||
var move = wrap(source).moveTo(wrapRotated(destination, 4), 64);
|
||||
assertEquals(32, move);
|
||||
|
||||
assertThat("Source is empty", source.getItem(0), isStack(ItemStack.EMPTY));
|
||||
+21
-138
@@ -4,34 +4,22 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.platform.FabricContainerTransfer;
|
||||
import dan200.computercraft.shared.platform.FabricItemContainer;
|
||||
import dan200.computercraft.shared.platform.ItemContainer;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedSlottedStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
|
||||
|
||||
/**
|
||||
* Inventory methods for Fabric's {@link SlottedStorage} and {@link ItemVariant}s.
|
||||
* <p>
|
||||
@@ -42,15 +30,14 @@ import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
|
||||
public final class InventoryMethods extends AbstractInventoryMethods<InventoryMethods.StorageWrapper> {
|
||||
/**
|
||||
* Wrapper over a {@link SlottedStorage}.
|
||||
*
|
||||
*/
|
||||
public static final class StorageWrapper {
|
||||
private final Storage<ItemVariant> storage;
|
||||
private final @Nullable SlottedStorage<ItemVariant> slottedStorage;
|
||||
private final FabricItemContainer container;
|
||||
|
||||
public StorageWrapper(Storage<ItemVariant> storage) {
|
||||
this.storage = storage;
|
||||
this.slottedStorage = storage instanceof SlottedStorage<ItemVariant> slotted ? slotted : null;
|
||||
this.container = new FabricItemContainer(storage);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -81,105 +68,26 @@ public final class InventoryMethods extends AbstractInventoryMethods<InventoryMe
|
||||
public int hashCode() {
|
||||
return storage instanceof CombinedSlottedStorage<?, ?> cs ? cs.parts.hashCode() : storage.hashCode();
|
||||
}
|
||||
}
|
||||
|
||||
private int size() {
|
||||
return slottedStorage != null ? slottedStorage.getSlotCount() : Iterables.size(storage);
|
||||
@Override
|
||||
protected ItemContainer getContainer(StorageWrapper inventory) {
|
||||
return inventory.container;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ItemContainer getContainer(IPeripheral peripheral) {
|
||||
var object = peripheral.getTarget();
|
||||
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
|
||||
|
||||
if (object instanceof BlockEntity blockEntity) {
|
||||
if (blockEntity.isRemoved()) return null;
|
||||
|
||||
var found = extractContainerImpl(blockEntity.getLevel(), blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
if (found != null) return new FabricItemContainer(found);
|
||||
}
|
||||
|
||||
private StorageView<ItemVariant> getSlot(int slot) {
|
||||
if (slottedStorage != null) return slottedStorage.getSlot(slot);
|
||||
|
||||
var singleSlot = Iterables.get(storage, slot);
|
||||
if (singleSlot == null) throw new IndexOutOfBoundsException();
|
||||
return singleSlot;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int size(StorageWrapper inventory) {
|
||||
return inventory.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<Integer, Map<String, ?>> list(StorageWrapper inventory) {
|
||||
Map<Integer, Map<String, ?>> result = new HashMap<>();
|
||||
var i = 0;
|
||||
for (var slots = inventory.storage.iterator(); slots.hasNext(); i++) {
|
||||
var stack = toStack(slots.next());
|
||||
if (!stack.isEmpty()) result.put(i + 1, VanillaDetailRegistries.ITEM_STACK.getBasicDetails(stack));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<String, ?> getItemDetail(StorageWrapper inventory, int slot) throws LuaException {
|
||||
assertBetween(slot, 1, inventory.size(), "Slot out of range (%s)");
|
||||
|
||||
var stack = toStack(inventory.getSlot(slot - 1));
|
||||
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public long getItemLimit(StorageWrapper inventory, int slot) throws LuaException {
|
||||
assertBetween(slot, 1, inventory.size(), "Slot out of range (%s)");
|
||||
return inventory.getSlot(slot - 1).getCapacity();
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pushItems(
|
||||
StorageWrapper from, IComputerAccess computer,
|
||||
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
var location = computer.getAvailablePeripheral(toName);
|
||||
if (location == null) throw new LuaException("Target '" + toName + "' does not exist");
|
||||
|
||||
var to = extractHandler(location);
|
||||
if (to == null) throw new LuaException("Target '" + toName + "' is not an inventory");
|
||||
|
||||
return moveImpl(from, fromSlot, to, toSlot, limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pullItems(
|
||||
StorageWrapper to, IComputerAccess computer,
|
||||
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
var location = computer.getAvailablePeripheral(fromName);
|
||||
if (location == null) throw new LuaException("Source '" + fromName + "' does not exist");
|
||||
|
||||
var from = extractHandler(location);
|
||||
if (from == null) throw new LuaException("Source '" + fromName + "' is not an inventory");
|
||||
|
||||
return moveImpl(from, fromSlot, to, toSlot, limit);
|
||||
}
|
||||
|
||||
private static int moveImpl(StorageWrapper from, int fromSlot, StorageWrapper to, Optional<Integer> toSlot, Optional<Integer> limit) throws LuaException {
|
||||
assertBetween(fromSlot, 1, from.size(), "From slot out of range (%s)");
|
||||
var fromStorage = from.getSlot(fromSlot - 1);
|
||||
|
||||
Storage<ItemVariant> toStorage;
|
||||
if (toSlot.isPresent()) {
|
||||
if (to.slottedStorage == null) throw new LuaException("Cannot specify toSlot for this inventory");
|
||||
assertBetween(toSlot.get(), 1, to.size(), "To slot out of range (%s)");
|
||||
toStorage = to.slottedStorage.getSlot(toSlot.get() - 1);
|
||||
} else {
|
||||
toStorage = to.storage;
|
||||
}
|
||||
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
if (actualLimit <= 0) return 0;
|
||||
|
||||
return Math.max(0, FabricContainerTransfer.move(fromStorage, toStorage, actualLimit));
|
||||
return null;
|
||||
}
|
||||
|
||||
public static @Nullable StorageWrapper extractContainer(Level level, BlockPos pos, BlockState state, @Nullable BlockEntity blockEntity, @Nullable Direction direction) {
|
||||
@@ -194,29 +102,4 @@ public final class InventoryMethods extends AbstractInventoryMethods<InventoryMe
|
||||
|
||||
return direction != null ? ItemStorage.SIDED.find(level, pos, state, blockEntity, direction) : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static StorageWrapper extractHandler(IPeripheral peripheral) {
|
||||
var object = peripheral.getTarget();
|
||||
var direction = peripheral instanceof dan200.computercraft.shared.peripheral.generic.GenericPeripheral sided ? sided.side() : null;
|
||||
|
||||
if (object instanceof BlockEntity blockEntity) {
|
||||
if (blockEntity.isRemoved()) return null;
|
||||
|
||||
var found = extractContainer(blockEntity.getLevel(), blockEntity.getBlockPos(), blockEntity.getBlockState(), blockEntity, direction);
|
||||
if (found != null) return found;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static ItemStack toStack(StorageView<ItemVariant> variant) {
|
||||
if (variant.isResourceBlank() || variant.getAmount() <= 0) return ItemStack.EMPTY;
|
||||
return toStack(variant.getResource(), variant.getAmount());
|
||||
}
|
||||
|
||||
private static ItemStack toStack(ItemVariant variant, long amount) {
|
||||
// I don't care about supporting this much power creep :D
|
||||
return variant.toStack(amount >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) amount);
|
||||
}
|
||||
}
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
// SPDX-FileCopyrightText: 2026 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
abstract class BasicSlottedStorage implements SlottedStorage<ItemVariant> {
|
||||
@Override
|
||||
public final long insert(ItemVariant resource, long maxAmount, TransactionContext transaction) {
|
||||
long amount = 0;
|
||||
|
||||
for (int i = 0, size = getSlotCount(); i < size; i++) {
|
||||
amount += getSlot(i).insert(resource, maxAmount - amount, transaction);
|
||||
if (amount == maxAmount) break;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final long extract(ItemVariant resource, long maxAmount, TransactionContext transaction) {
|
||||
long amount = 0;
|
||||
|
||||
for (int i = 0, size = getSlotCount(); i < size; i++) {
|
||||
amount += getSlot(i).extract(resource, maxAmount - amount, transaction);
|
||||
if (amount == maxAmount) break;
|
||||
}
|
||||
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Iterator<StorageView<ItemVariant>> iterator() {
|
||||
return new Iterator<>() {
|
||||
int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < getSlotCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageView<ItemVariant> next() {
|
||||
var slot = i++;
|
||||
if (slot >= getSlotCount()) throw new NoSuchElementException();
|
||||
return getSlot(slot);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
-172
@@ -1,172 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class FabricContainerTransfer implements ContainerTransfer {
|
||||
private final Storage<ItemVariant> storage;
|
||||
|
||||
private FabricContainerTransfer(Storage<ItemVariant> storage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
public static ContainerTransfer of(Storage<ItemVariant> storage) {
|
||||
return storage instanceof SlottedStorage<ItemVariant> inv ? new SlottedImpl(inv) : new FabricContainerTransfer(storage);
|
||||
}
|
||||
|
||||
public static ContainerTransfer.Slotted of(SlottedStorage<ItemVariant> storage) {
|
||||
return new SlottedImpl(storage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item from a slot into another storage.
|
||||
*
|
||||
* @param from The slot to move from.
|
||||
* @param to The storage to move to.
|
||||
* @param maxAmount The max amount to move.
|
||||
* @return The number of items which were transferred, or one of {@link #NO_ITEMS} or {@link #NO_SPACE}. This will
|
||||
* <em>NEVER</em> return 0.
|
||||
* @see StorageUtil#move(Storage, Storage, Predicate, long, TransactionContext)
|
||||
* @see #moveTo(ContainerTransfer, int)
|
||||
*/
|
||||
public static int move(StorageView<ItemVariant> from, Storage<ItemVariant> to, long maxAmount) {
|
||||
var resource = from.getResource();
|
||||
if (resource.isBlank()) return NO_ITEMS;
|
||||
|
||||
try (var transaction = Transaction.openOuter()) {
|
||||
// Check how much can be extracted and inserted.
|
||||
var maxExtracted = StorageUtil.simulateExtract(from, resource, maxAmount, transaction);
|
||||
if (maxExtracted == 0) return NO_ITEMS;
|
||||
|
||||
var accepted = to.insert(resource, maxExtracted, transaction);
|
||||
if (accepted == 0) return NO_SPACE;
|
||||
|
||||
// Extract or rollback.
|
||||
if (from.extract(resource, accepted, transaction) == accepted) {
|
||||
transaction.commit();
|
||||
return accepted > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) accepted;
|
||||
}
|
||||
|
||||
return NO_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int moveTo(ContainerTransfer destination, int maxAmount) {
|
||||
var hasItem = false;
|
||||
var destStorage = ((FabricContainerTransfer) destination).storage;
|
||||
for (var slot : storage.nonEmptyViews()) {
|
||||
var result = move(slot, destStorage, maxAmount);
|
||||
if (result >= 0) return result;
|
||||
|
||||
if (result == NO_SPACE) hasItem = true;
|
||||
}
|
||||
|
||||
return hasItem ? NO_SPACE : NO_ITEMS;
|
||||
}
|
||||
|
||||
private static final class SlottedImpl extends FabricContainerTransfer implements ContainerTransfer.Slotted {
|
||||
private final SlottedStorage<ItemVariant> storage;
|
||||
|
||||
SlottedImpl(SlottedStorage<ItemVariant> storage) {
|
||||
super(storage);
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContainerTransfer rotate(int offset) {
|
||||
return offset == 0 ? this : of(new OffsetStorage(storage, offset));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContainerTransfer singleSlot(int slot) {
|
||||
return of(storage.getSlot(slot));
|
||||
}
|
||||
}
|
||||
|
||||
private record OffsetStorage(SlottedStorage<ItemVariant> storage, int offset) implements Storage<ItemVariant> {
|
||||
@Override
|
||||
public boolean supportsInsertion() {
|
||||
for (var slot : storage.getSlots()) {
|
||||
if (slot.supportsInsertion()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsExtraction() {
|
||||
for (var slot : storage.getSlots()) {
|
||||
if (slot.supportsExtraction()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insert(ItemVariant resource, long maxAmount, TransactionContext transaction) {
|
||||
var slots = storage.getSlots();
|
||||
var size = slots.size();
|
||||
|
||||
long transferred = 0;
|
||||
for (var i = 0; i < size; i++) {
|
||||
transferred += slots.get(wrap(i, size)).insert(resource, maxAmount - transferred, transaction);
|
||||
if (transferred >= maxAmount) break;
|
||||
}
|
||||
|
||||
return transferred;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extract(ItemVariant resource, long maxAmount, TransactionContext transaction) {
|
||||
var slots = storage.getSlots();
|
||||
var size = slots.size();
|
||||
|
||||
long transferred = 0;
|
||||
for (var i = 0; i < size; i++) {
|
||||
transferred += slots.get(wrap(i, size)).extract(resource, maxAmount - transferred, transaction);
|
||||
if (transferred >= maxAmount) break;
|
||||
}
|
||||
|
||||
return transferred;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<StorageView<ItemVariant>> iterator() {
|
||||
var slots = storage.getSlots();
|
||||
var size = slots.size();
|
||||
return new Iterator<>() {
|
||||
int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageView<ItemVariant> next() {
|
||||
var slot = i++;
|
||||
if (slot >= size) throw new NoSuchElementException();
|
||||
return slots.get(wrap(slot, size));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
int wrap(int slot, int size) {
|
||||
var actualSlot = slot + offset;
|
||||
return actualSlot >= size ? actualSlot - size : actualSlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Iterators;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.SlottedStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.Storage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageUtil;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StorageView;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public final class FabricItemContainer implements ItemContainer {
|
||||
private final Storage<ItemVariant> storage;
|
||||
private final @Nullable SlottedStorage<ItemVariant> slottedStorage;
|
||||
|
||||
public FabricItemContainer(SlottedStorage<ItemVariant> storage) {
|
||||
this.storage = this.slottedStorage = storage;
|
||||
}
|
||||
|
||||
public FabricItemContainer(Storage<ItemVariant> storage) {
|
||||
this.storage = storage;
|
||||
this.slottedStorage = storage instanceof SlottedStorage<ItemVariant> slotted ? slotted : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return slottedStorage != null ? slottedStorage.getSlotCount() : Iterables.size(storage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ItemStack> contents() {
|
||||
return Iterators.transform(storage.iterator(), FabricItemContainer::toStack);
|
||||
}
|
||||
|
||||
private StorageView<ItemVariant> getSlot(int slot) {
|
||||
if (slottedStorage != null) return slottedStorage.getSlot(slot);
|
||||
|
||||
var singleSlot = Iterables.get(storage, slot);
|
||||
if (singleSlot == null) throw new IndexOutOfBoundsException();
|
||||
return singleSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItem(int slot) {
|
||||
return toStack(getSlot(slot));
|
||||
}
|
||||
|
||||
private static ItemStack toStack(StorageView<ItemVariant> variant) {
|
||||
if (variant.isResourceBlank() || variant.getAmount() <= 0) return ItemStack.EMPTY;
|
||||
return variant.getResource().toStack(saturatingToInt(variant.getAmount()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapacity(int slot) {
|
||||
return saturatingToInt(getSlot(slot).getCapacity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ItemContainer singleSlot(int slot) {
|
||||
return slottedStorage != null ? new FabricItemContainer(slottedStorage.getSlot(slot)) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int moveTo(ItemContainer destination, int maxAmount) {
|
||||
var hasItems = false;
|
||||
var destStorage = ((FabricItemContainer) destination).storage;
|
||||
for (var slot : storage.nonEmptyViews()) {
|
||||
var result = move(slot, destStorage, maxAmount);
|
||||
if (result >= 0) return result;
|
||||
|
||||
if (result == NO_SPACE) hasItems = true;
|
||||
}
|
||||
|
||||
return hasItems ? NO_SPACE : NO_ITEMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int moveTo(int fromSlot, ItemContainer destination, int maxAmount) {
|
||||
return move(getSlot(fromSlot), ((FabricItemContainer) destination).storage, maxAmount);
|
||||
}
|
||||
|
||||
private static int move(StorageView<ItemVariant> from, Storage<ItemVariant> to, long maxAmount) {
|
||||
var resource = from.getResource();
|
||||
if (resource.isBlank()) return NO_ITEMS;
|
||||
|
||||
try (var transaction = Transaction.openOuter()) {
|
||||
// Check how much can be extracted and inserted.
|
||||
var maxExtracted = StorageUtil.simulateExtract(from, resource, maxAmount, transaction);
|
||||
if (maxExtracted == 0) return NO_ITEMS;
|
||||
|
||||
var accepted = to.insert(resource, maxExtracted, transaction);
|
||||
if (accepted == 0) return NO_SPACE;
|
||||
|
||||
// Extract or rollback.
|
||||
if (from.extract(resource, accepted, transaction) == accepted) {
|
||||
transaction.commit();
|
||||
return saturatingToInt(accepted);
|
||||
}
|
||||
|
||||
return NO_SPACE;
|
||||
}
|
||||
}
|
||||
|
||||
private static int saturatingToInt(long value) {
|
||||
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
|
||||
}
|
||||
|
||||
private static final class OffsetStorage extends BasicSlottedStorage {
|
||||
private final SlottedStorage<ItemVariant> storage;
|
||||
private final int offset;
|
||||
|
||||
private OffsetStorage(SlottedStorage<ItemVariant> storage, int offset) {
|
||||
this.storage = storage;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsInsertion() {
|
||||
return storage.supportsInsertion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsExtraction() {
|
||||
return storage.supportsExtraction();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getVersion() {
|
||||
return storage.getVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlotCount() {
|
||||
return storage.getSlotCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleSlotStorage<ItemVariant> getSlot(int slot) {
|
||||
var size = getSlotCount();
|
||||
Objects.checkIndex(slot, size);
|
||||
|
||||
var actualSlot = slot + offset;
|
||||
return storage.getSlot(actualSlot >= size ? actualSlot - size : actualSlot);
|
||||
}
|
||||
}
|
||||
|
||||
static ItemContainer rotated(SlottedStorage<ItemVariant> storage, int offset) {
|
||||
return new FabricItemContainer(new OffsetStorage(storage, offset));
|
||||
}
|
||||
}
|
||||
+10
-5
@@ -204,18 +204,23 @@ public class PlatformHelperImpl implements PlatformHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContainerTransfer.Slotted wrapContainer(Container container) {
|
||||
return FabricContainerTransfer.of(InventoryStorage.of(container, null));
|
||||
public ItemContainer wrapContainer(Container container) {
|
||||
return new FabricItemContainer(InventoryStorage.of(container, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer wrapContainerRotated(Container container, int offset) {
|
||||
return FabricItemContainer.rotated(InventoryStorage.of(container, null), offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public @Nullable ContainerTransfer getContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
public @Nullable ItemContainer getContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
var storage = ItemStorage.SIDED.find(level, pos, side);
|
||||
if (storage != null) return FabricContainerTransfer.of(storage);
|
||||
if (storage != null) return new FabricItemContainer(storage);
|
||||
|
||||
var entity = InventoryUtil.getEntityContainer(level, pos, side);
|
||||
return entity == null ? null : FabricContainerTransfer.of(InventoryStorage.of(entity, side));
|
||||
return entity == null ? null : new FabricItemContainer(InventoryStorage.of(entity, side));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import dan200.computercraft.test.shared.WithMinecraft;
|
||||
import dan200.computercraft.test.shared.platform.ContainerTransferContract;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
|
||||
import net.minecraft.world.Container;
|
||||
|
||||
@WithMinecraft
|
||||
public class FabricContainerTransferTest implements ContainerTransferContract {
|
||||
@Override
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public ContainerTransfer.Slotted wrap(Container container) {
|
||||
return FabricContainerTransfer.of(InventoryStorage.of(container, null));
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import dan200.computercraft.test.shared.WithMinecraft;
|
||||
import dan200.computercraft.test.shared.platform.ItemContainerContract;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
|
||||
import net.minecraft.world.Container;
|
||||
|
||||
@WithMinecraft
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
public class FabricItemContainerTest implements ItemContainerContract {
|
||||
@Override
|
||||
public ItemContainer wrap(Container container) {
|
||||
return new FabricItemContainer(InventoryStorage.of(container, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer wrapSlot(Container container, int slot) {
|
||||
return new FabricItemContainer(InventoryStorage.of(container, null).getSlot(slot));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer wrapRotated(Container container, int offset) {
|
||||
return FabricItemContainer.rotated(InventoryStorage.of(container, null), offset);
|
||||
}
|
||||
}
|
||||
+7
-103
@@ -4,12 +4,9 @@
|
||||
|
||||
package dan200.computercraft.shared.peripheral.generic.methods;
|
||||
|
||||
import dan200.computercraft.api.detail.VanillaDetailRegistries;
|
||||
import dan200.computercraft.api.lua.LuaException;
|
||||
import dan200.computercraft.api.lua.LuaFunction;
|
||||
import dan200.computercraft.api.peripheral.IComputerAccess;
|
||||
import dan200.computercraft.api.peripheral.IPeripheral;
|
||||
import dan200.computercraft.shared.platform.ForgeContainerTransfer;
|
||||
import dan200.computercraft.shared.platform.ForgeItemContainer;
|
||||
import dan200.computercraft.shared.platform.ItemContainer;
|
||||
import dan200.computercraft.shared.util.CapabilityUtil;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
@@ -19,94 +16,19 @@ import net.minecraftforge.items.IItemHandler;
|
||||
import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static dan200.computercraft.core.util.ArgumentHelpers.assertBetween;
|
||||
|
||||
/**
|
||||
* Inventory methods for Forge's {@link IItemHandler}.
|
||||
*/
|
||||
public final class InventoryMethods extends AbstractInventoryMethods<IItemHandler> {
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int size(IItemHandler inventory) {
|
||||
return inventory.getSlots();
|
||||
protected ItemContainer getContainer(IItemHandler inventory) {
|
||||
return new ForgeItemContainer(inventory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<Integer, Map<String, ?>> list(IItemHandler inventory) {
|
||||
Map<Integer, Map<String, ?>> result = new HashMap<>();
|
||||
var size = inventory.getSlots();
|
||||
for (var i = 0; i < size; i++) {
|
||||
var stack = inventory.getStackInSlot(i);
|
||||
if (!stack.isEmpty()) result.put(i + 1, VanillaDetailRegistries.ITEM_STACK.getBasicDetails(stack));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@LuaFunction(mainThread = true)
|
||||
public Map<String, ?> getItemDetail(IItemHandler inventory, int slot) throws LuaException {
|
||||
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
|
||||
|
||||
var stack = inventory.getStackInSlot(slot - 1);
|
||||
return stack.isEmpty() ? null : VanillaDetailRegistries.ITEM_STACK.getDetails(stack);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public long getItemLimit(IItemHandler inventory, int slot) throws LuaException {
|
||||
assertBetween(slot, 1, inventory.getSlots(), "Slot out of range (%s)");
|
||||
return inventory.getSlotLimit(slot - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pushItems(
|
||||
IItemHandler from, IComputerAccess computer,
|
||||
String toName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
var location = computer.getAvailablePeripheral(toName);
|
||||
if (location == null) throw new LuaException("Target '" + toName + "' does not exist");
|
||||
|
||||
var to = extractHandler(location);
|
||||
if (to == null) throw new LuaException("Target '" + toName + "' is not an inventory");
|
||||
|
||||
// Validate slots
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
assertBetween(fromSlot, 1, from.getSlots(), "From slot out of range (%s)");
|
||||
if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.getSlots(), "To slot out of range (%s)");
|
||||
|
||||
if (actualLimit <= 0) return 0;
|
||||
return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LuaFunction(mainThread = true)
|
||||
public int pullItems(
|
||||
IItemHandler to, IComputerAccess computer,
|
||||
String fromName, int fromSlot, Optional<Integer> limit, Optional<Integer> toSlot
|
||||
) throws LuaException {
|
||||
// Find location to transfer to
|
||||
var location = computer.getAvailablePeripheral(fromName);
|
||||
if (location == null) throw new LuaException("Source '" + fromName + "' does not exist");
|
||||
|
||||
var from = extractHandler(location);
|
||||
if (from == null) throw new LuaException("Source '" + fromName + "' is not an inventory");
|
||||
|
||||
// Validate slots
|
||||
int actualLimit = limit.orElse(Integer.MAX_VALUE);
|
||||
assertBetween(fromSlot, 1, from.getSlots(), "From slot out of range (%s)");
|
||||
if (toSlot.isPresent()) assertBetween(toSlot.get(), 1, to.getSlots(), "To slot out of range (%s)");
|
||||
|
||||
if (actualLimit <= 0) return 0;
|
||||
return moveItem(from, fromSlot - 1, to, toSlot.orElse(0) - 1, actualLimit);
|
||||
protected @Nullable ItemContainer getContainer(IPeripheral peripheral) {
|
||||
var handler = extractHandler(peripheral);
|
||||
return handler == null ? null : new ForgeItemContainer(handler);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -125,22 +47,4 @@ public final class InventoryMethods extends AbstractInventoryMethods<IItemHandle
|
||||
if (object instanceof Container container) return new InvWrapper(container);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an item from one handler to another.
|
||||
*
|
||||
* @param from The handler to move from.
|
||||
* @param fromSlot The slot to move from.
|
||||
* @param to The handler to move to.
|
||||
* @param toSlot The slot to move to. Use any number < 0 to represent any slot.
|
||||
* @param limit The max number to move. {@link Integer#MAX_VALUE} for no limit.
|
||||
* @return The number of items moved.
|
||||
*/
|
||||
private static int moveItem(IItemHandler from, int fromSlot, IItemHandler to, int toSlot, final int limit) {
|
||||
var fromWrapper = new ForgeContainerTransfer(from).singleSlot(fromSlot);
|
||||
var toWrapper = new ForgeContainerTransfer(to);
|
||||
if (toSlot >= 0) toWrapper = toWrapper.singleSlot(toSlot);
|
||||
|
||||
return Math.max(0, fromWrapper.moveTo(toWrapper, limit));
|
||||
}
|
||||
}
|
||||
|
||||
-90
@@ -1,90 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
|
||||
public class ForgeContainerTransfer implements ContainerTransfer.Slotted {
|
||||
private final IItemHandler handler;
|
||||
private final int offset;
|
||||
private final int limit;
|
||||
private final int slots;
|
||||
|
||||
public ForgeContainerTransfer(IItemHandler handler) {
|
||||
this(handler, 0, handler.getSlots(), handler.getSlots());
|
||||
}
|
||||
|
||||
public ForgeContainerTransfer(IItemHandler handler, int offset, int limit, int slots) {
|
||||
this.handler = handler;
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
this.slots = slots;
|
||||
}
|
||||
|
||||
private int mapSlot(int slot) {
|
||||
if (slot < 0 || slot >= limit) throw new IllegalArgumentException("slot is out of bounds");
|
||||
|
||||
slot += offset;
|
||||
if (slot >= slots) slot -= limit;
|
||||
return slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForgeContainerTransfer rotate(int offset) {
|
||||
return offset == 0 ? this : new ForgeContainerTransfer(handler, mapSlot(offset), limit, slots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ForgeContainerTransfer singleSlot(int slot) {
|
||||
return slot == 0 && limit == 1 ? this : new ForgeContainerTransfer(handler, mapSlot(slot), 1, slots);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int moveTo(ContainerTransfer destination, int maxAmount) {
|
||||
return moveItem(this, (ForgeContainerTransfer) destination, maxAmount);
|
||||
}
|
||||
|
||||
public static int moveItem(ForgeContainerTransfer src, ForgeContainerTransfer dest, int maxAmount) {
|
||||
var targetSlot = 0;
|
||||
|
||||
var movedStack = ItemStack.EMPTY;
|
||||
var moved = 0;
|
||||
|
||||
outer:
|
||||
for (var srcSlot = 0; srcSlot < src.limit; srcSlot++) {
|
||||
var actualSrcSlot = src.mapSlot(srcSlot);
|
||||
var stack = src.handler.extractItem(actualSrcSlot, maxAmount, true);
|
||||
if (stack.isEmpty()) continue;
|
||||
|
||||
// Pick the first item in the inventory to be the one we transfer, skipping those that match.
|
||||
if (movedStack.isEmpty()) {
|
||||
movedStack = stack.copy();
|
||||
if (stack.getMaxStackSize() < maxAmount) maxAmount = stack.getMaxStackSize();
|
||||
} else if (!ItemStack.isSameItemSameTags(stack, movedStack)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (; targetSlot < dest.limit; targetSlot++) {
|
||||
var oldCount = stack.getCount();
|
||||
stack = dest.handler.insertItem(dest.mapSlot(targetSlot), stack, false);
|
||||
|
||||
var transferred = oldCount - stack.getCount();
|
||||
var extracted = src.handler.extractItem(actualSrcSlot, transferred, false);
|
||||
|
||||
moved += transferred;
|
||||
|
||||
// We failed to extract as much as we should have. This should never happen, but goodness knows.
|
||||
if (extracted.getCount() < transferred) break outer;
|
||||
|
||||
if (moved >= maxAmount) return moved;
|
||||
if (stack.isEmpty()) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (moved == 0) return movedStack.isEmpty() ? NO_ITEMS : NO_SPACE;
|
||||
return moved;
|
||||
}
|
||||
}
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraftforge.items.IItemHandler;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class ForgeItemContainer implements ItemContainer {
|
||||
private final IItemHandler handler;
|
||||
|
||||
public ForgeItemContainer(IItemHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return handler.getSlots();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<ItemStack> contents() {
|
||||
class ContentsIterator implements Iterator<ItemStack> {
|
||||
private int slot;
|
||||
private final int size;
|
||||
private final IItemHandler handler;
|
||||
|
||||
ContentsIterator(IItemHandler handler) {
|
||||
this.handler = handler;
|
||||
this.size = handler.getSlots();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return slot < size;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack next() {
|
||||
return handler.getStackInSlot(this.slot++);
|
||||
}
|
||||
}
|
||||
|
||||
return new ContentsIterator(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack getItem(int slot) {
|
||||
return handler.getStackInSlot(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCapacity(int slot) {
|
||||
return handler.getSlotLimit(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer singleSlot(int slot) {
|
||||
Objects.checkIndex(slot, size());
|
||||
return handler instanceof SingleSlotWrapper ? this : new ForgeItemContainer(new SingleSlotWrapper(handler, slot));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int moveTo(ItemContainer destination, int maxAmount) {
|
||||
var hasItems = false;
|
||||
var destHandler = ((ForgeItemContainer) destination).handler;
|
||||
for (int fromSlot = 0, fromSize = handler.getSlots(); fromSlot < fromSize; fromSlot++) {
|
||||
var result = move(handler, fromSlot, destHandler, maxAmount);
|
||||
if (result >= 0) return result;
|
||||
|
||||
if (result == NO_SPACE) hasItems = true;
|
||||
}
|
||||
|
||||
return hasItems ? NO_SPACE : NO_ITEMS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int moveTo(int fromSlot, ItemContainer destination, int maxAmount) {
|
||||
return move(handler, fromSlot, ((ForgeItemContainer) destination).handler, maxAmount);
|
||||
}
|
||||
|
||||
public static int move(IItemHandler from, int fromSlot, IItemHandler to, int maxAmount) {
|
||||
var stack = from.extractItem(fromSlot, saturatingToInt(maxAmount), true);
|
||||
if (stack.isEmpty()) return NO_ITEMS;
|
||||
|
||||
// Pick the first item in the inventory to be the one we transfer, skipping those that match.
|
||||
if (stack.getMaxStackSize() < maxAmount) maxAmount = stack.getMaxStackSize();
|
||||
|
||||
long moved = 0;
|
||||
for (int toSlot = 0, toSize = to.getSlots(); toSlot < toSize; toSlot++) {
|
||||
var oldCount = stack.getCount();
|
||||
stack = to.insertItem(toSlot, stack, false);
|
||||
|
||||
var transferred = oldCount - stack.getCount();
|
||||
if (transferred == 0) continue;
|
||||
|
||||
var extracted = from.extractItem(fromSlot, transferred, false);
|
||||
|
||||
moved += transferred;
|
||||
|
||||
// We failed to extract as much as we should have. This should never happen, but goodness knows.
|
||||
if (extracted.getCount() < transferred) break;
|
||||
|
||||
// Abort if we've extracted all our items, abort.
|
||||
if (moved >= maxAmount) break;
|
||||
if (stack.isEmpty()) break;
|
||||
}
|
||||
|
||||
return moved == 0 ? NO_SPACE : saturatingToInt(moved);
|
||||
}
|
||||
|
||||
private static int saturatingToInt(long value) {
|
||||
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
|
||||
}
|
||||
|
||||
private abstract static class MappedSlotWrapper implements IItemHandler {
|
||||
final IItemHandler handler;
|
||||
|
||||
private MappedSlotWrapper(IItemHandler handler) {
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
abstract int mapSlot(int slot);
|
||||
|
||||
@Override
|
||||
public final ItemStack getStackInSlot(int slot) {
|
||||
return handler.getStackInSlot(mapSlot(slot));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final int getSlotLimit(int slot) {
|
||||
return handler.getSlotLimit(mapSlot(slot));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ItemStack insertItem(int slot, ItemStack stack, boolean simulate) {
|
||||
return handler.insertItem(mapSlot(slot), stack, simulate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final ItemStack extractItem(int slot, int limit, boolean simulate) {
|
||||
return handler.extractItem(mapSlot(slot), limit, simulate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isItemValid(int slot, ItemStack stack) {
|
||||
return handler.isItemValid(mapSlot(slot), stack);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SingleSlotWrapper extends MappedSlotWrapper {
|
||||
private final int actualSlot;
|
||||
|
||||
private SingleSlotWrapper(IItemHandler handler, int slot) {
|
||||
super(handler);
|
||||
this.actualSlot = slot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlots() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
int mapSlot(int slot) {
|
||||
Objects.checkIndex(slot, 1);
|
||||
return actualSlot;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class RotatedWrapper extends MappedSlotWrapper {
|
||||
private final int offset;
|
||||
|
||||
RotatedWrapper(IItemHandler handler, int offset) {
|
||||
super(handler);
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSlots() {
|
||||
return handler.getSlots();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int mapSlot(int slot) {
|
||||
var size = getSlots();
|
||||
Objects.checkIndex(slot, size);
|
||||
|
||||
var actualSlot = slot + offset;
|
||||
return actualSlot >= size ? actualSlot - size : actualSlot;
|
||||
}
|
||||
}
|
||||
|
||||
static ItemContainer rotated(IItemHandler handler, int offset) {
|
||||
return new ForgeItemContainer(new RotatedWrapper(handler, offset));
|
||||
}
|
||||
}
|
||||
+11
-6
@@ -194,29 +194,34 @@ public class PlatformHelperImpl implements PlatformHelper {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContainerTransfer.Slotted wrapContainer(Container container) {
|
||||
return new ForgeContainerTransfer(new InvWrapper(container));
|
||||
public ItemContainer wrapContainer(Container container) {
|
||||
return new ForgeItemContainer(new InvWrapper(container));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer wrapContainerRotated(Container container, int offset) {
|
||||
return ForgeItemContainer.rotated(new InvWrapper(container), offset);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ContainerTransfer getContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
public ItemContainer getContainer(ServerLevel level, BlockPos pos, Direction side) {
|
||||
var block = level.getBlockState(pos);
|
||||
if (block.getBlock() instanceof WorldlyContainerHolder holder) {
|
||||
var container = holder.getContainer(block, level, pos);
|
||||
return new ForgeContainerTransfer(new SidedInvWrapper(container, side));
|
||||
return new ForgeItemContainer(new SidedInvWrapper(container, side));
|
||||
}
|
||||
|
||||
var blockEntity = level.getBlockEntity(pos);
|
||||
if (blockEntity != null) {
|
||||
var inventory = blockEntity.getCapability(ForgeCapabilities.ITEM_HANDLER, side);
|
||||
if (inventory.isPresent()) {
|
||||
return new ForgeContainerTransfer(inventory.orElseThrow(NullPointerException::new));
|
||||
return new ForgeItemContainer(inventory.orElseThrow(NullPointerException::new));
|
||||
}
|
||||
}
|
||||
|
||||
var entity = InventoryUtil.getEntityContainer(level, pos, side);
|
||||
return entity == null ? null : new ForgeContainerTransfer(new InvWrapper(entity));
|
||||
return entity == null ? null : new ForgeItemContainer(new InvWrapper(entity));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import dan200.computercraft.test.shared.WithMinecraft;
|
||||
import dan200.computercraft.test.shared.platform.ContainerTransferContract;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
|
||||
@WithMinecraft
|
||||
public class ForgeContainerTransferTest implements ContainerTransferContract {
|
||||
@Override
|
||||
public ContainerTransfer.Slotted wrap(Container container) {
|
||||
return new ForgeContainerTransfer(new InvWrapper(container));
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
// SPDX-FileCopyrightText: 2022 The CC: Tweaked Developers
|
||||
//
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package dan200.computercraft.shared.platform;
|
||||
|
||||
import dan200.computercraft.test.shared.WithMinecraft;
|
||||
import dan200.computercraft.test.shared.platform.ItemContainerContract;
|
||||
import net.minecraft.world.Container;
|
||||
import net.minecraftforge.items.wrapper.InvWrapper;
|
||||
|
||||
@WithMinecraft
|
||||
public class ForgeItemContainerTest implements ItemContainerContract {
|
||||
@Override
|
||||
public ItemContainer wrap(Container container) {
|
||||
return new ForgeItemContainer(new InvWrapper(container));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer wrapSlot(Container container, int slot) {
|
||||
return wrap(container).singleSlot(slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemContainer wrapRotated(Container container, int offset) {
|
||||
return ForgeItemContainer.rotated(new InvWrapper(container), offset);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user