Skip to content

Commit

Permalink
first steps toward container item support
Browse files Browse the repository at this point in the history
  • Loading branch information
BasiqueEvangelist committed Sep 15, 2024
1 parent 32573c0 commit 9ff9f58
Show file tree
Hide file tree
Showing 7 changed files with 384 additions and 1 deletion.
3 changes: 2 additions & 1 deletion fabric-transfer-api-v1/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
])
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@
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.ComposterWrapper;
import net.fabricmc.fabric.impl.transfer.item.ContainerComponentStorage;
import net.fabricmc.fabric.mixin.transfer.DoubleInventoryAccessor;

/**
Expand Down Expand Up @@ -80,6 +84,9 @@ 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);

public static final ItemApiLookup<Storage<ItemVariant>, ContainerItemContext> ITEM =
ItemApiLookup.get(Identifier.of("fabric", "item_storage"), Storage.asClass(), ContainerItemContext.class);

private ItemStorage() {
}

Expand Down Expand Up @@ -128,5 +135,7 @@ private ItemStorage() {

return inventoryToWrap != null ? InventoryStorage.of(inventoryToWrap, direction) : null;
});

ItemStorage.ITEM.registerForItems((itemStack, context) -> new ContainerComponentStorage(context, 27), Items.SHULKER_BOX);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* 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 org.jetbrains.annotations.UnmodifiableView;

import net.minecraft.component.ComponentChanges;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.ContainerComponent;
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.SlottedStorage;
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
import net.fabricmc.fabric.api.transfer.v1.storage.base.CombinedStorage;
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 CombinedStorage<ItemVariant, SingleSlotStorage<ItemVariant>> implements SlottedStorage<ItemVariant> {
final ContainerItemContext ctx;

public ContainerComponentStorage(ContainerItemContext ctx, int slots) {
super(Collections.emptyList());
this.ctx = ctx;

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

@Override
public @UnmodifiableView List<SingleSlotStorage<ItemVariant>> getSlots() {
return parts;
}

@Override
public int getSlotCount() {
return parts.size();
}

@Override
public SingleSlotStorage<ItemVariant> getSlot(int slot) {
return parts.get(slot);
}

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

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

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().getRegistryEntry().getIdAsString(), slot);
}
}
}
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"BucketItemAccessor",
"BucketItemMixin",
"ChiseledBookshelfBlockEntityMixin",
"ContainerComponentAccessor",
"CrafterBlockMixin",
"DoubleInventoryAccessor",
"DropperBlockMixin",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/*
* 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.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 = ItemStorage.ITEM.find(stack, ContainerItemContext.withConstant(stack));

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 = ItemStorage.ITEM.find(sourceStorage.stack, ContainerItemContext.ofSingleSlot(sourceStorage));

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

0 comments on commit 9ff9f58

Please sign in to comment.