From 5f3d7c8bc5bf411348e52218131d291b45dc95b9 Mon Sep 17 00:00:00 2001 From: Basique Date: Wed, 11 Sep 2024 16:12:25 +0300 Subject: [PATCH] add bundle support --- .../api/transfer/v1/item/ItemStorage.java | 24 ++- .../transfer/item/BundleContentsStorage.java | 180 ++++++++++++++++++ .../BundleContentsComponentAccessor.java | 32 ++++ .../fabric-transfer-api-v1.mixins.json | 1 + .../unittests/ContainerItemTests.java | 43 +++++ 5 files changed, 279 insertions(+), 1 deletion(-) create mode 100644 fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/BundleContentsStorage.java create mode 100644 fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/BundleContentsComponentAccessor.java 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 005b8c8374..93469a12e7 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 @@ -40,6 +40,7 @@ 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; @@ -136,6 +137,27 @@ private ItemStorage() { return inventoryToWrap != null ? InventoryStorage.of(inventoryToWrap, direction) : null; }); - ItemStorage.ITEM.registerForItems((itemStack, context) -> new ContainerComponentStorage(context, 27), Items.SHULKER_BOX); + 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); } } 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 0000000000..2d8d1f5aca --- /dev/null +++ b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/BundleContentsStorage.java @@ -0,0 +1,180 @@ +/* + * 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 org.jetbrains.annotations.NotNull; + +import net.minecraft.component.ComponentChanges; +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.BundleContentsComponent; +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 { + private final ContainerItemContext ctx; + private final List slotCache = new ArrayList<>(); + private List> slots = List.of(); + + public BundleContentsStorage(ContainerItemContext ctx) { + this.ctx = ctx; + } + + 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 (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); + + 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); + + updateSlotsIfNeeded(); + + long amount = 0; + + for (StorageView slot : slots) { + amount += slot.extract(resource, maxAmount - amount, transaction); + if (amount == maxAmount) break; + } + + return amount; + } + + @Override + public @NotNull Iterator> iterator() { + updateSlotsIfNeeded(); + + return slots.iterator(); + } + + private void updateSlotsIfNeeded() { + if (slots.size() != bundleContents().size()) { + while (bundleContents().size() > slotCache.size()) { + slotCache.add(new BundleSlotWrapper(slotCache.size())); + } + + slots = Collections.unmodifiableList(slotCache.subList(0, bundleContents().size())); + } + } + + BundleContentsComponent bundleContents() { + return ctx.getItemVariant().getComponentMap().getOrDefault(DataComponentTypes.BUNDLE_CONTENTS, BundleContentsComponent.DEFAULT); + } + + private class BundleSlotWrapper implements StorageView { + private final int index; + + private BundleSlotWrapper(int index) { + this.index = index; + } + + private ItemStack getStack() { + if (bundleContents().size() <= index) return ItemStack.EMPTY; + + return ((List) bundleContents().iterate()).get(index); + } + + @Override + public long extract(ItemVariant resource, long maxAmount, TransactionContext transaction) { + StoragePreconditions.notNegative(maxAmount); + + if (bundleContents().size() <= index) return 0; + if (!resource.matches(getStack())) return 0; + + var stacksCopy = new ArrayList<>((Collection) 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); + + var builder = new BundleContentsComponent.Builder(BundleContentsComponent.DEFAULT); + + for (ItemStack stack : stacksCopy) builder.add(stack); + + ComponentChanges changes = ComponentChanges.builder() + .add(DataComponentTypes.BUNDLE_CONTENTS, builder.build()) + .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/mixin/transfer/BundleContentsComponentAccessor.java b/fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/mixin/transfer/BundleContentsComponentAccessor.java new file mode 100644 index 0000000000..43ec46e855 --- /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/resources/fabric-transfer-api-v1.mixins.json b/fabric-transfer-api-v1/src/main/resources/fabric-transfer-api-v1.mixins.json index 43649932f6..cb45454e1d 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,6 +6,7 @@ "AbstractFurnaceBlockEntityMixin", "BucketItemAccessor", "BucketItemMixin", + "BundleContentsComponentAccessor", "ChiseledBookshelfBlockEntityMixin", "ContainerComponentAccessor", "CrafterBlockMixin", 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 index 1f9f95536f..37a4a0ca7f 100644 --- 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 @@ -29,6 +29,7 @@ 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 { @@ -76,4 +77,46 @@ public void setStack(ItemStack stack) { 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 storage = ItemStorage.ITEM.find(sourceStorage.stack, ContainerItemContext.ofSingleSlot(sourceStorage)); + + Assertions.assertNotNull(storage, "Bundle didn't have a Storage"); + + 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 view = storage.nonEmptyIterator().next(); + Assertions.assertEquals(29, view.getAmount()); + } + } }