Skip to content

Commit

Permalink
add bundle support
Browse files Browse the repository at this point in the history
  • Loading branch information
BasiqueEvangelist committed Sep 15, 2024
1 parent 9ff9f58 commit 5f3d7c8
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<ItemVariant> {
private final ContainerItemContext ctx;
private final List<BundleSlotWrapper> slotCache = new ArrayList<>();
private List<StorageView<ItemVariant>> 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<ItemVariant> slot : slots) {
amount += slot.extract(resource, maxAmount - amount, transaction);
if (amount == maxAmount) break;
}

return amount;
}

@Override
public @NotNull Iterator<StorageView<ItemVariant>> 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<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 (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);

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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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!");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"AbstractFurnaceBlockEntityMixin",
"BucketItemAccessor",
"BucketItemMixin",
"BundleContentsComponentAccessor",
"ChiseledBookshelfBlockEntityMixin",
"ContainerComponentAccessor",
"CrafterBlockMixin",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<ItemVariant> storage = ItemStorage.ITEM.find(sourceStorage.stack, ContainerItemContext.ofSingleSlot(sourceStorage));

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());
}
}
}

0 comments on commit 5f3d7c8

Please sign in to comment.