mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-03 10:39:57 -04:00
Add support for Item-containing Items (#4083)
* first steps toward container item support
* add bundle support
* Update fabric-transfer-api-v1/src/main/java/net/fabricmc/fabric/impl/transfer/item/ContainerComponentStorage.java
Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>
* address reviews + TIL ContainerItemContext#find exists
* address reviews of bundle code
---------
Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>
(cherry picked from commit 4054068033
)
This commit is contained in:
parent
83a9d3130d
commit
c0bdb897d1
9 changed files with 755 additions and 1 deletions
fabric-transfer-api-v1
build.gradle
src
main
java/net/fabricmc/fabric
api/transfer/v1/item
impl/transfer/item
mixin/transfer
resources
test/java/net/fabricmc/fabric/test/transfer/unittests
testmod/java/net/fabricmc/fabric/test/transfer/ingame
|
@ -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'
|
||||
])
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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!");
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -6,7 +6,9 @@
|
|||
"AbstractFurnaceBlockEntityMixin",
|
||||
"BucketItemAccessor",
|
||||
"BucketItemMixin",
|
||||
"BundleContentsComponentAccessor",
|
||||
"ChiseledBookshelfBlockEntityMixin",
|
||||
"ContainerComponentAccessor",
|
||||
"CrafterBlockMixin",
|
||||
"DoubleInventoryAccessor",
|
||||
"DropperBlockMixin",
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue