diff --git a/fabric-transfer-api-v1/build.gradle b/fabric-transfer-api-v1/build.gradle index 9f81ef61b..042140be8 100644 --- a/fabric-transfer-api-v1/build.gradle +++ b/fabric-transfer-api-v1/build.gradle @@ -11,5 +11,6 @@ moduleDependencies(project, [ testDependencies(project, [ ':fabric-object-builder-api-v1', ':fabric-rendering-v1', - ':fabric-resource-loader-v0' + ':fabric-resource-loader-v0', + ':fabric-command-api-v2' ]) diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/item/ItemStorage.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/item/ItemStorage.java index b51d15d96..f4edcd71b 100644 --- a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/item/ItemStorage.java +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/api/transfer/v1/item/ItemStorage.java @@ -27,17 +27,22 @@ import net.minecraft.block.entity.ChestBlockEntity; import net.minecraft.inventory.Inventory; import net.minecraft.inventory.SidedInventory; import net.minecraft.inventory.SimpleInventory; +import net.minecraft.item.Items; import net.minecraft.util.Identifier; import net.minecraft.util.math.Direction; import net.fabricmc.fabric.api.lookup.v1.block.BlockApiLookup; +import net.fabricmc.fabric.api.lookup.v1.item.ItemApiLookup; +import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; import net.fabricmc.fabric.api.transfer.v1.item.base.SingleStackStorage; 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.base.CombinedSlottedStorage; import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage; import net.fabricmc.fabric.api.transfer.v1.storage.base.SidedStorageBlockEntity; +import net.fabricmc.fabric.impl.transfer.item.BundleContentsStorage; import net.fabricmc.fabric.impl.transfer.item.ComposterWrapper; +import net.fabricmc.fabric.impl.transfer.item.ContainerComponentStorage; import net.fabricmc.fabric.mixin.transfer.DoubleInventoryAccessor; /** @@ -80,6 +85,16 @@ public final class ItemStorage { public static final BlockApiLookup<Storage<ItemVariant>, @Nullable Direction> SIDED = BlockApiLookup.get(Identifier.of("fabric", "sided_item_storage"), Storage.asClass(), Direction.class); + /** + * Item access to item variant storages. + * Querying should happen through {@link ContainerItemContext#find}. + * + * <p>This may be queried both client-side and server-side. + * Returned APIs should behave the same regardless of the logical side. + */ + public static final ItemApiLookup<Storage<ItemVariant>, ContainerItemContext> ITEM = + ItemApiLookup.get(Identifier.of("fabric", "item_storage"), Storage.asClass(), ContainerItemContext.class); + private ItemStorage() { } @@ -128,5 +143,47 @@ public final class ItemStorage { return inventoryToWrap != null ? InventoryStorage.of(inventoryToWrap, direction) : null; }); + + ItemStorage.ITEM.registerForItems( + (itemStack, context) -> new ContainerComponentStorage(context, 27), + Items.SHULKER_BOX, + Items.WHITE_SHULKER_BOX, + Items.ORANGE_SHULKER_BOX, + Items.MAGENTA_SHULKER_BOX, + Items.LIGHT_BLUE_SHULKER_BOX, + Items.YELLOW_SHULKER_BOX, + Items.LIME_SHULKER_BOX, + Items.PINK_SHULKER_BOX, + Items.GRAY_SHULKER_BOX, + Items.LIGHT_GRAY_SHULKER_BOX, + Items.CYAN_SHULKER_BOX, + Items.PURPLE_SHULKER_BOX, + Items.BLUE_SHULKER_BOX, + Items.BROWN_SHULKER_BOX, + Items.GREEN_SHULKER_BOX, + Items.RED_SHULKER_BOX, + Items.BLACK_SHULKER_BOX + ); + + ItemStorage.ITEM.registerForItems( + (itemStack, context) -> new BundleContentsStorage(context), + Items.BUNDLE, + Items.WHITE_BUNDLE, + Items.ORANGE_BUNDLE, + Items.MAGENTA_BUNDLE, + Items.LIGHT_BLUE_BUNDLE, + Items.YELLOW_BUNDLE, + Items.LIME_BUNDLE, + Items.PINK_BUNDLE, + Items.GRAY_BUNDLE, + Items.LIGHT_GRAY_BUNDLE, + Items.CYAN_BUNDLE, + Items.PURPLE_BUNDLE, + Items.BLUE_BUNDLE, + Items.BROWN_BUNDLE, + Items.GREEN_BUNDLE, + Items.RED_BUNDLE, + Items.BLACK_BUNDLE + ); } } diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/BundleContentsStorage.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/BundleContentsStorage.java new file mode 100644 index 000000000..358444e7b --- /dev/null +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/BundleContentsStorage.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.transfer.item; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.lang3.math.Fraction; + +import net.minecraft.component.ComponentChanges; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.BundleContentsComponent; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; +import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; +import net.fabricmc.fabric.api.transfer.v1.storage.Storage; +import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions; +import net.fabricmc.fabric.api.transfer.v1.storage.StorageView; +import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; +import net.fabricmc.fabric.mixin.transfer.BundleContentsComponentAccessor; + +public class BundleContentsStorage implements Storage<ItemVariant> { + private final ContainerItemContext ctx; + private final List<BundleSlotWrapper> slotCache = new ArrayList<>(); + private List<StorageView<ItemVariant>> slots = List.of(); + private final Item originalItem; + + public BundleContentsStorage(ContainerItemContext ctx) { + this.ctx = ctx; + this.originalItem = ctx.getItemVariant().getItem(); + } + + private boolean updateStack(ComponentChanges changes, TransactionContext transaction) { + ItemVariant newVariant = ctx.getItemVariant().withComponentChanges(changes); + return ctx.exchange(newVariant, 1, transaction) > 0; + } + + @Override + public long insert(ItemVariant resource, long maxAmount, TransactionContext transaction) { + StoragePreconditions.notBlankNotNegative(resource, maxAmount); + + if (!isStillValid()) return 0; + + if (maxAmount > Integer.MAX_VALUE) maxAmount = Integer.MAX_VALUE; + + ItemStack stack = resource.toStack((int) maxAmount); + + if (!BundleContentsComponent.canBeBundled(stack)) return 0; + + var builder = new BundleContentsComponent.Builder(bundleContents()); + + int inserted = builder.add(stack); + + if (inserted == 0) return 0; + + ComponentChanges changes = ComponentChanges.builder() + .add(DataComponentTypes.BUNDLE_CONTENTS, builder.build()) + .build(); + + if (!updateStack(changes, transaction)) return 0; + + return inserted; + } + + @Override + public long extract(ItemVariant resource, long maxAmount, TransactionContext transaction) { + StoragePreconditions.notNegative(maxAmount); + + if (!isStillValid()) return 0; + + updateSlotsIfNeeded(); + + long amount = 0; + + for (StorageView<ItemVariant> slot : slots) { + amount += slot.extract(resource, maxAmount - amount, transaction); + if (amount == maxAmount) break; + } + + return amount; + } + + @Override + public Iterator<StorageView<ItemVariant>> iterator() { + updateSlotsIfNeeded(); + + return slots.iterator(); + } + + private boolean isStillValid() { + return ctx.getItemVariant().getItem() == originalItem; + } + + private void updateSlotsIfNeeded() { + int bundleSize = bundleContents().size(); + + if (slots.size() != bundleSize) { + while (bundleSize > slotCache.size()) { + slotCache.add(new BundleSlotWrapper(slotCache.size())); + } + + slots = Collections.unmodifiableList(slotCache.subList(0, bundleSize)); + } + } + + BundleContentsComponent bundleContents() { + return ctx.getItemVariant().getComponentMap().getOrDefault(DataComponentTypes.BUNDLE_CONTENTS, BundleContentsComponent.DEFAULT); + } + + private class BundleSlotWrapper implements StorageView<ItemVariant> { + private final int index; + + private BundleSlotWrapper(int index) { + this.index = index; + } + + private ItemStack getStack() { + if (bundleContents().size() <= index) return ItemStack.EMPTY; + + return ((List<ItemStack>) bundleContents().iterate()).get(index); + } + + @Override + public long extract(ItemVariant resource, long maxAmount, TransactionContext transaction) { + StoragePreconditions.notNegative(maxAmount); + + if (!BundleContentsStorage.this.isStillValid()) return 0; + if (bundleContents().size() <= index) return 0; + if (!resource.matches(getStack())) return 0; + + var stacksCopy = new ArrayList<>((Collection<ItemStack>) bundleContents().iterateCopy()); + + int extracted = (int) Math.min(stacksCopy.get(index).getCount(), maxAmount); + + stacksCopy.get(index).decrement(extracted); + if (stacksCopy.get(index).isEmpty()) stacksCopy.remove(index); + + ComponentChanges changes = ComponentChanges.builder() + .add(DataComponentTypes.BUNDLE_CONTENTS, new BundleContentsComponent(stacksCopy)) + .build(); + + if (!updateStack(changes, transaction)) return 0; + + return extracted; + } + + @Override + public boolean isResourceBlank() { + return getStack().isEmpty(); + } + + @Override + public ItemVariant getResource() { + return ItemVariant.of(getStack()); + } + + @Override + public long getAmount() { + return getStack().getCount(); + } + + @Override + public long getCapacity() { + Fraction remainingSpace = Fraction.ONE.subtract(bundleContents().getOccupancy()); + int extraAllowed = Math.max( + remainingSpace.divideBy(BundleContentsComponentAccessor.getOccupancy(getStack())).intValue(), + 0 + ); + return getAmount() + extraAllowed; + } + } +} diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ContainerComponentStorage.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ContainerComponentStorage.java new file mode 100644 index 000000000..4a483d0f0 --- /dev/null +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ContainerComponentStorage.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.impl.transfer.item; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import net.minecraft.component.ComponentChanges; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ContainerComponent; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; + +import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; +import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant; +import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions; +import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedSlottedStorage; +import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage; +import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext; +import net.fabricmc.fabric.mixin.transfer.ContainerComponentAccessor; + +public class ContainerComponentStorage extends CombinedSlottedStorage<ItemVariant, SingleSlotStorage<ItemVariant>> { + final ContainerItemContext ctx; + private final Item originalItem; + + public ContainerComponentStorage(ContainerItemContext ctx, int slots) { + super(Collections.emptyList()); + this.ctx = ctx; + this.originalItem = ctx.getItemVariant().getItem(); + + List<ContainerSlotWrapper> backingList = new ArrayList<>(slots); + + for (int i = 0; i < slots; i++) { + backingList.add(new ContainerSlotWrapper(i)); + } + + parts = Collections.unmodifiableList(backingList); + } + + ContainerComponent container() { + return ctx.getItemVariant().getComponentMap().getOrDefault(DataComponentTypes.CONTAINER, ContainerComponent.DEFAULT); + } + + ContainerComponentAccessor containerAccessor() { + return (ContainerComponentAccessor) (Object) container(); + } + + private boolean isStillValid() { + return ctx.getItemVariant().getItem() == originalItem; + } + + private class ContainerSlotWrapper implements SingleSlotStorage<ItemVariant> { + final int slot; + + ContainerSlotWrapper(int slot) { + this.slot = slot; + } + + private ItemStack getStack() { + List<ItemStack> stacks = ContainerComponentStorage.this.containerAccessor().fabric_getStacks(); + + if (stacks.size() <= slot) return ItemStack.EMPTY; + + return stacks.get(slot); + } + + protected boolean setStack(ItemStack stack, TransactionContext transaction) { + List<ItemStack> stacks = ContainerComponentStorage.this.container().stream().collect(Collectors.toList()); + + while (stacks.size() <= slot) stacks.add(ItemStack.EMPTY); + + stacks.set(slot, stack); + + ContainerItemContext ctx = ContainerComponentStorage.this.ctx; + + ItemVariant newVariant = ctx.getItemVariant().withComponentChanges(ComponentChanges.builder() + .add(DataComponentTypes.CONTAINER, ContainerComponent.fromStacks(stacks)) + .build()); + + return ctx.exchange(newVariant, 1, transaction) == 1; + } + + @Override + public long insert(ItemVariant insertedVariant, long maxAmount, TransactionContext transaction) { + StoragePreconditions.notBlankNotNegative(insertedVariant, maxAmount); + + if (!ContainerComponentStorage.this.isStillValid()) return 0; + + ItemStack currentStack = getStack(); + + if ((insertedVariant.matches(currentStack) || currentStack.isEmpty()) && insertedVariant.getItem().canBeNested()) { + int insertedAmount = (int) Math.min(maxAmount, getCapacity() - currentStack.getCount()); + + if (insertedAmount > 0) { + currentStack = getStack().copy(); + + if (currentStack.isEmpty()) { + currentStack = insertedVariant.toStack(insertedAmount); + } else { + currentStack.increment(insertedAmount); + } + + if (!setStack(currentStack, transaction)) return 0; + + return insertedAmount; + } + } + + return 0; + } + + @Override + public long extract(ItemVariant variant, long maxAmount, TransactionContext transaction) { + StoragePreconditions.notBlankNotNegative(variant, maxAmount); + + if (!ContainerComponentStorage.this.isStillValid()) return 0; + + ItemStack currentStack = getStack(); + + if (variant.matches(currentStack)) { + int extracted = (int) Math.min(currentStack.getCount(), maxAmount); + + if (extracted > 0) { + currentStack = getStack().copy(); + currentStack.decrement(extracted); + + if (!setStack(currentStack, transaction)) return 0; + + return extracted; + } + } + + return 0; + } + + @Override + public boolean isResourceBlank() { + return getStack().isEmpty(); + } + + @Override + public ItemVariant getResource() { + return ItemVariant.of(getStack()); + } + + @Override + public long getAmount() { + return getStack().getCount(); + } + + @Override + public long getCapacity() { + return getStack().getItem().getMaxCount(); + } + + @Override + public String toString() { + return "ContainerSlotWrapper[%s#%d]".formatted(ContainerComponentStorage.this.ctx.getItemVariant(), slot); + } + } +} diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/BundleContentsComponentAccessor.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/BundleContentsComponentAccessor.java new file mode 100644 index 000000000..43ec46e85 --- /dev/null +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/BundleContentsComponentAccessor.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.transfer; + +import org.apache.commons.lang3.math.Fraction; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import net.minecraft.component.type.BundleContentsComponent; +import net.minecraft.item.ItemStack; + +@Mixin(BundleContentsComponent.class) +public interface BundleContentsComponentAccessor { + @Invoker("getOccupancy") + static Fraction getOccupancy(ItemStack stack) { + throw new AssertionError("This shouldn't happen!"); + } +} diff --git a/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/ContainerComponentAccessor.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/ContainerComponentAccessor.java new file mode 100644 index 000000000..41793e7f3 --- /dev/null +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/ContainerComponentAccessor.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.mixin.transfer; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import net.minecraft.component.type.ContainerComponent; +import net.minecraft.item.ItemStack; +import net.minecraft.util.collection.DefaultedList; + +@Mixin(ContainerComponent.class) +public interface ContainerComponentAccessor { + @Accessor("stacks") + DefaultedList<ItemStack> fabric_getStacks(); +} diff --git a/fabric-transfer-api-v1/src/main/resources/fabric-transfer-api-v1.mixins.json b/fabric-transfer-api-v1/src/main/resources/fabric-transfer-api-v1.mixins.json index 4952c8429..26e1a54a3 100644 --- a/fabric-transfer-api-v1/src/main/resources/fabric-transfer-api-v1.mixins.json +++ b/fabric-transfer-api-v1/src/main/resources/fabric-transfer-api-v1.mixins.json @@ -6,7 +6,9 @@ "AbstractFurnaceBlockEntityMixin", "BucketItemAccessor", "BucketItemMixin", + "BundleContentsComponentAccessor", "ChiseledBookshelfBlockEntityMixin", + "ContainerComponentAccessor", "CrafterBlockMixin", "DoubleInventoryAccessor", "DropperBlockMixin", diff --git a/fabric-transfer-api-v1/src/test/java/net/fabricmc/fabric/test/transfer/unittests/ContainerItemTests.java b/fabric-transfer-api-v1/src/test/java/net/fabricmc/fabric/test/transfer/unittests/ContainerItemTests.java new file mode 100644 index 000000000..a81388b41 --- /dev/null +++ b/fabric-transfer-api-v1/src/test/java/net/fabricmc/fabric/test/transfer/unittests/ContainerItemTests.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2016, 2017, 2018, 2019 FabricMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.fabricmc.fabric.test.transfer.unittests; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; + +import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; +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.item.base.SingleStackStorage; +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.transaction.Transaction; + +class ContainerItemTests extends AbstractTransferApiTest { + @BeforeAll + static void beforeAll() { + bootstrap(); + } + + @Test + public void emptyShulkerBox() { + ItemStack stack = new ItemStack(Items.SHULKER_BOX); + Storage<ItemVariant> storage = ContainerItemContext.withConstant(stack).find(ItemStorage.ITEM); + + Assertions.assertInstanceOf(SlottedStorage.class, storage); + Assertions.assertEquals(27, ((SlottedStorage<ItemVariant>) storage).getSlotCount()); + } + + @Test + public void insertAndExtractShulkerBox() { + var sourceStorage = new SingleStackStorage() { + public ItemStack stack = new ItemStack(Items.SHULKER_BOX); + + @Override + protected ItemStack getStack() { + return stack; + } + + @Override + public void setStack(ItemStack stack) { + this.stack = stack; + } + }; + + Storage<ItemVariant> storage = ContainerItemContext.ofSingleSlot(sourceStorage).find(ItemStorage.ITEM); + + Assertions.assertNotNull(storage, "Shulker Box didn't have a Storage<ItemVariant>"); + + try (var tx = Transaction.openOuter()) { + Assertions.assertEquals(20, storage.insert(ItemVariant.of(Items.NETHER_STAR), 20, tx)); + tx.commit(); + } + + try (var tx = Transaction.openOuter()) { + Assertions.assertEquals(20, storage.extract(ItemVariant.of(Items.NETHER_STAR), 64, tx)); + tx.commit(); + } + } + + @Test + public void bundle() { + var sourceStorage = new SingleStackStorage() { + public ItemStack stack = new ItemStack(Items.BUNDLE); + + @Override + protected ItemStack getStack() { + return stack; + } + + @Override + public void setStack(ItemStack stack) { + this.stack = stack; + } + }; + + Storage<ItemVariant> storage = ContainerItemContext.ofSingleSlot(sourceStorage).find(ItemStorage.ITEM); + + Assertions.assertNotNull(storage, "Bundle didn't have a Storage<ItemVariant>"); + + try (Transaction tx = Transaction.openOuter()) { + long inserted1 = storage.insert(ItemVariant.of(Items.NETHER_STAR), 200, tx); + Assertions.assertEquals(64, inserted1); + + long inserted2 = storage.insert(ItemVariant.of(Items.STONE), 40, tx); + Assertions.assertEquals(0, inserted2); + + tx.commit(); + } + + try (Transaction tx = Transaction.openOuter()) { + long extracted1 = storage.extract(ItemVariant.of(Items.STONE), 60, tx); + Assertions.assertEquals(0, extracted1); + + long extracted2 = storage.extract(ItemVariant.of(Items.NETHER_STAR), 35, tx); + Assertions.assertEquals(35, extracted2); + + StorageView<ItemVariant> view = storage.nonEmptyIterator().next(); + Assertions.assertEquals(29, view.getAmount()); + } + } + + @Test + public void shulkerBoxWrongItem() { + var sourceStorage = new SingleStackStorage() { + public ItemStack stack = new ItemStack(Items.SHULKER_BOX); + + @Override + protected ItemStack getStack() { + return stack; + } + + @Override + public void setStack(ItemStack stack) { + this.stack = stack; + } + }; + + Storage<ItemVariant> storage = ContainerItemContext.ofSingleSlot(sourceStorage).find(ItemStorage.ITEM); + + Assertions.assertNotNull(storage, "Shulker Box didn't have a Storage<ItemVariant>"); + + try (var tx = Transaction.openOuter()) { + Assertions.assertEquals(20, storage.insert(ItemVariant.of(Items.NETHER_STAR), 20, tx)); + } + + sourceStorage.setStack(new ItemStack(Items.NETHER_STAR)); + + try (var tx = Transaction.openOuter()) { + Assertions.assertEquals(0, storage.insert(ItemVariant.of(Items.NETHER_STAR), 20, tx)); + } + } + + @Test + public void bundleWrongItem() { + var sourceStorage = new SingleStackStorage() { + public ItemStack stack = new ItemStack(Items.BUNDLE); + + @Override + protected ItemStack getStack() { + return stack; + } + + @Override + public void setStack(ItemStack stack) { + this.stack = stack; + } + }; + + Storage<ItemVariant> storage = ContainerItemContext.ofSingleSlot(sourceStorage).find(ItemStorage.ITEM); + + Assertions.assertNotNull(storage, "Bundle didn't have a Storage<ItemVariant>"); + + try (Transaction tx = Transaction.openOuter()) { + long inserted1 = storage.insert(ItemVariant.of(Items.NETHER_STAR), 200, tx); + Assertions.assertEquals(64, inserted1); + } + + sourceStorage.setStack(new ItemStack(Items.NETHER_STAR)); + + try (var tx = Transaction.openOuter()) { + Assertions.assertEquals(0, storage.insert(ItemVariant.of(Items.NETHER_STAR), 200, tx)); + } + } +} diff --git a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/TransferTestInitializer.java b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/TransferTestInitializer.java index d9a81ffd7..261b230ab 100644 --- a/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/TransferTestInitializer.java +++ b/fabric-transfer-api-v1/src/testmod/java/net/fabricmc/fabric/test/transfer/ingame/TransferTestInitializer.java @@ -16,20 +16,31 @@ package net.fabricmc.fabric.test.transfer.ingame; +import com.mojang.brigadier.arguments.LongArgumentType; + import net.minecraft.block.AbstractBlock; import net.minecraft.block.Block; import net.minecraft.block.Blocks; import net.minecraft.block.entity.BlockEntityType; +import net.minecraft.command.argument.ItemStackArgumentType; import net.minecraft.item.BlockItem; import net.minecraft.item.Item; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; +import net.minecraft.server.command.CommandManager; +import net.minecraft.text.Text; +import net.minecraft.util.Hand; import net.minecraft.util.Identifier; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.fabric.api.object.builder.v1.block.entity.FabricBlockEntityTypeBuilder; +import net.fabricmc.fabric.api.transfer.v1.context.ContainerItemContext; import net.fabricmc.fabric.api.transfer.v1.fluid.FluidStorage; 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.Storage; +import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction; public class TransferTestInitializer implements ModInitializer { public static final String MOD_ID = "fabric-transfer-api-v1-testmod"; @@ -57,6 +68,74 @@ public class TransferTestInitializer implements ModInitializer { ItemStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> TrashingStorage.ITEM, Blocks.OBSIDIAN); // And diamond ore blocks are an infinite source of diamonds! Yay! ItemStorage.SIDED.registerForBlocks((world, pos, state, be, direction) -> CreativeStorage.DIAMONDS, Blocks.DIAMOND_ORE); + + CommandRegistrationCallback.EVENT.register((dispatcher, registryAccess, environment) -> { + dispatcher.register( + CommandManager.literal("fabric_insertintoheldstack") + .then(CommandManager.argument("stack", ItemStackArgumentType.itemStack(registryAccess)) + .then(CommandManager.argument("count", LongArgumentType.longArg(1)) + .executes(context -> { + ItemVariant variant = ItemVariant.of(ItemStackArgumentType.getItemStackArgument(context, "stack") + .createStack(1, false)); + + ContainerItemContext containerCtx = ContainerItemContext.ofPlayerHand(context.getSource().getPlayerOrThrow(), Hand.MAIN_HAND); + Storage<ItemVariant> storage = containerCtx.find(ItemStorage.ITEM); + + if (storage == null) { + context.getSource().sendMessage(Text.literal("no storage found")); + return 0; + } + + long inserted; + + try (Transaction tx = Transaction.openOuter()) { + inserted = storage.insert( + variant, + LongArgumentType.getLong(context, "count"), + tx + ); + tx.commit(); + } + + context.getSource().sendMessage(Text.literal("inserted " + inserted + " items")); + + return (int) inserted; + }))) + ); + + dispatcher.register( + CommandManager.literal("fabric_extractfromheldstack") + .then(CommandManager.argument("stack", ItemStackArgumentType.itemStack(registryAccess)) + .then(CommandManager.argument("count", LongArgumentType.longArg(1)) + .executes(context -> { + ItemVariant variant = ItemVariant.of(ItemStackArgumentType.getItemStackArgument(context, "stack") + .createStack(1, false)); + + ContainerItemContext containerCtx = ContainerItemContext.ofPlayerHand(context.getSource().getPlayerOrThrow(), Hand.MAIN_HAND); + Storage<ItemVariant> storage = containerCtx.find(ItemStorage.ITEM); + + if (storage == null) { + context.getSource().sendMessage(Text.literal("no storage found")); + return 0; + } + + long extracted; + + try (Transaction tx = Transaction.openOuter()) { + extracted = storage.extract( + variant, + LongArgumentType.getLong(context, "count"), + tx + ); + tx.commit(); + } + + context.getSource().sendMessage(Text.literal("extracted " + extracted + " items")); + + return (int) extracted; + }))) + ); + }); } private static void registerBlock(Block block, String name) {