mirror of
https://github.com/FabricMC/fabric.git
synced 2025-04-08 21:14:41 -04:00
Two Transfer API fixes (#2818)
* Fix #2810: Double chest wrapper not always updating both halves
* Fix #2522: Make creative ContainerItemContext give unique items to the play
(cherry picked from commit ccd377ba6b
)
This commit is contained in:
parent
61111d0e8a
commit
413cbbc764
14 changed files with 343 additions and 22 deletions
fabric-command-api-v2/src/testmod/java/net/fabricmc/fabric/test/command
fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen
fabric-recipe-api-v1/src
main
testmod/java/net/fabricmc/fabric/test/recipe/ingredient
fabric-transfer-api-v1/src
main/java/net/fabricmc/fabric
api/transfer/v1
impl/transfer
testmod
java/net/fabricmc/fabric/test/transfer
resources/data/fabric-transfer-api-v1-testmod/gametest/structures
|
@ -54,8 +54,14 @@ public class EntitySelectorGameTest {
|
|||
context.expectEntitiesAround(EntityType.CREEPER, BlockPos.ORIGIN, 3, 2.0);
|
||||
MinecraftServer server = context.getWorld().getServer();
|
||||
int result = server.getCommandManager().executeWithPrefix(server.getCommandSource(), command);
|
||||
context.assertTrue(result == 2, "Expected 2 entities killed, got " + result);
|
||||
assertTrue(context, result == 2, "Expected 2 entities killed, got " + result);
|
||||
context.expectEntitiesAround(EntityType.CREEPER, BlockPos.ORIGIN, 1, 2.0);
|
||||
context.complete();
|
||||
}
|
||||
|
||||
private static void assertTrue(TestContext context, boolean condition, String message) {
|
||||
if (!condition) {
|
||||
context.throwGameTestException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,9 +28,6 @@ import java.util.Optional;
|
|||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.recipe.Ingredient;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@ -43,6 +40,7 @@ import net.minecraft.data.server.recipe.RecipeJsonProvider;
|
|||
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
|
||||
import net.minecraft.entity.EntityType;
|
||||
import net.minecraft.entity.attribute.EntityAttributes;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.loot.LootPool;
|
||||
import net.minecraft.loot.LootTable;
|
||||
|
@ -50,6 +48,7 @@ import net.minecraft.loot.LootTables;
|
|||
import net.minecraft.loot.context.LootContextTypes;
|
||||
import net.minecraft.loot.entry.ItemEntry;
|
||||
import net.minecraft.loot.provider.number.ConstantLootNumberProvider;
|
||||
import net.minecraft.recipe.Ingredient;
|
||||
import net.minecraft.tag.BlockTags;
|
||||
import net.minecraft.tag.ItemTags;
|
||||
import net.minecraft.tag.TagKey;
|
||||
|
@ -141,7 +140,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
// - 9 undamaged pickaxes should match.
|
||||
// - 1 undamaged pickaxe + 8 damaged pickaxes should match (regardless of the position).
|
||||
// - 1 undamaged renamed pickaxe + 8 damaged pickaxes should match (NBT is not strictly matched here).
|
||||
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.DIAMOND_BLOCK)
|
||||
ShapelessRecipeJsonBuilder.create(Items.DIAMOND_BLOCK)
|
||||
.input(Ingredient.ofItems(Items.DIAMOND_PICKAXE))
|
||||
.input(Ingredient.ofItems(Items.DIAMOND_PICKAXE))
|
||||
.input(Ingredient.ofItems(Items.DIAMOND_PICKAXE))
|
||||
|
@ -160,14 +159,14 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
ItemStack appleWithGoldenName = new ItemStack(Items.APPLE);
|
||||
appleWithGoldenName.setCustomName(Text.literal("Golden Apple"));
|
||||
appleWithGoldenName.setRepairCost(0);
|
||||
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.GOLDEN_APPLE)
|
||||
ShapelessRecipeJsonBuilder.create(Items.GOLDEN_APPLE)
|
||||
.input(DefaultCustomIngredients.nbt(appleWithGoldenName, true))
|
||||
.criterion("has_apple", conditionsFromItem(Items.APPLE))
|
||||
.offerTo(exporter);
|
||||
|
||||
// Test AND
|
||||
// To test: charcoal should give a torch, but coal should not.
|
||||
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.TORCH)
|
||||
ShapelessRecipeJsonBuilder.create(Items.TORCH)
|
||||
// charcoal only
|
||||
.input(DefaultCustomIngredients.all(Ingredient.fromTag(ItemTags.COALS), Ingredient.ofItems(Items.CHARCOAL)))
|
||||
.criterion("has_charcoal", conditionsFromItem(Items.CHARCOAL))
|
||||
|
@ -175,7 +174,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
|
||||
// Test OR
|
||||
// To test: a golden pickaxe or a golden shovel should give a block of gold.
|
||||
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.GOLD_BLOCK)
|
||||
ShapelessRecipeJsonBuilder.create(Items.GOLD_BLOCK)
|
||||
.input(DefaultCustomIngredients.any(Ingredient.ofItems(Items.GOLDEN_PICKAXE), Ingredient.ofItems(Items.GOLDEN_SHOVEL)))
|
||||
.criterion("has_pickaxe", conditionsFromItem(Items.GOLDEN_PICKAXE))
|
||||
.criterion("has_shovel", conditionsFromItem(Items.GOLDEN_SHOVEL))
|
||||
|
@ -183,7 +182,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
|
||||
// Test difference
|
||||
// To test: only copper, netherite and emerald should match the recipe.
|
||||
ShapelessRecipeJsonBuilder.create(RecipeCategory.MISC, Items.BEACON)
|
||||
ShapelessRecipeJsonBuilder.create(Items.BEACON)
|
||||
.input(DefaultCustomIngredients.difference(
|
||||
DefaultCustomIngredients.any(
|
||||
Ingredient.fromTag(ItemTags.BEACON_PAYMENT_ITEMS),
|
||||
|
@ -277,7 +276,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
@Override
|
||||
protected void generateTags() {
|
||||
getOrCreateTagBuilder(BlockTags.FIRE).add(SIMPLE_BLOCK);
|
||||
getOrCreateTagBuilder(BlockTags.ANVIL).setReplace(true).add(SIMPLE_BLOCK, BLOCK_WITHOUT_ITEM);
|
||||
getOrCreateTagBuilder(BlockTags.DIRT).setReplace(true).add(SIMPLE_BLOCK, BLOCK_WITHOUT_ITEM);
|
||||
getOrCreateTagBuilder(BlockTags.ACACIA_LOGS).forceAddTag(BlockTags.ANIMALS_SPAWNABLE_ON);
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +288,7 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
|
|||
|
||||
@Override
|
||||
protected void generateTags() {
|
||||
copy(BlockTags.ANVIL, ItemTags.ANVIL);
|
||||
copy(BlockTags.DIRT, ItemTags.DIRT);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,6 @@ import net.minecraft.inventory.CraftingInventory;
|
|||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.recipe.Ingredient;
|
||||
import net.minecraft.recipe.ShapelessRecipe;
|
||||
import net.minecraft.recipe.book.CraftingRecipeCategory;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.minecraft.util.collection.DefaultedList;
|
||||
import net.minecraft.world.World;
|
||||
|
@ -48,7 +47,7 @@ public class ShapelessRecipeMixin {
|
|||
private boolean fabric_requiresTesting = false;
|
||||
|
||||
@Inject(at = @At("RETURN"), method = "<init>")
|
||||
private void cacheRequiresTesting(Identifier id, String group, CraftingRecipeCategory category, ItemStack output, DefaultedList<Ingredient> input, CallbackInfo ci) {
|
||||
private void cacheRequiresTesting(Identifier id, String group, ItemStack output, DefaultedList<Ingredient> input, CallbackInfo ci) {
|
||||
for (Ingredient ingredient : input) {
|
||||
if (ingredient.requiresTesting()) {
|
||||
fabric_requiresTesting = true;
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
],
|
||||
"accessWidener": "fabric-recipe-api-v1.accesswidener",
|
||||
"depends": {
|
||||
"fabricloader": ">=0.14.10",
|
||||
"fabricloader": ">=0.14.9",
|
||||
"fabric-networking-api-v1": "*"
|
||||
},
|
||||
"entrypoints": {
|
||||
|
|
|
@ -44,7 +44,7 @@ public class ShapelessRecipeMatchTests {
|
|||
|
||||
CraftingInventory craftingInv = new CraftingInventory(new ScreenHandler(null, 0) {
|
||||
@Override
|
||||
public ItemStack quickMove(PlayerEntity player, int slot) {
|
||||
public ItemStack transferSlot(PlayerEntity player, int slot) {
|
||||
return ItemStack.EMPTY;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable;
|
|||
import net.minecraft.entity.player.PlayerEntity;
|
||||
import net.minecraft.entity.player.PlayerInventory;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.ItemUsage;
|
||||
import net.minecraft.screen.ScreenHandler;
|
||||
import net.minecraft.util.Hand;
|
||||
|
||||
|
@ -35,6 +36,8 @@ import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
|
|||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.Transaction;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
import net.fabricmc.fabric.impl.transfer.context.ConstantContainerItemContext;
|
||||
import net.fabricmc.fabric.impl.transfer.context.CreativeInteractionContainerItemContext;
|
||||
import net.fabricmc.fabric.impl.transfer.context.InitialContentsContainerItemContext;
|
||||
import net.fabricmc.fabric.impl.transfer.context.PlayerContainerItemContext;
|
||||
import net.fabricmc.fabric.impl.transfer.context.SingleSlotContainerItemContext;
|
||||
|
@ -90,17 +93,29 @@ public interface ContainerItemContext {
|
|||
/**
|
||||
* Returns a context for interaction with a player's hand. This is recommended for item use interactions.
|
||||
*
|
||||
* <p>In creative mode, {@link #withInitial(ItemStack)} is used to avoid modifying the item in hand.
|
||||
* <p>In creative mode, {@link #forCreativeInteraction} is used with the hand stack.
|
||||
* Otherwise, {@link #ofPlayerHand} is used.
|
||||
* This matches the behavior of {@link ItemUsage#exchangeStack}.
|
||||
*/
|
||||
static ContainerItemContext forPlayerInteraction(PlayerEntity player, Hand hand) {
|
||||
if (player.getAbilities().creativeMode) {
|
||||
return withInitial(player.getStackInHand(hand));
|
||||
return forCreativeInteraction(player, player.getStackInHand(hand));
|
||||
} else {
|
||||
return ofPlayerHand(player, hand);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a context for creative interaction.
|
||||
*
|
||||
* <p>The stack will never be modified, and any updated stack will only be added to the player's inventory
|
||||
* if the player's inventory doesn't already contain it.
|
||||
* This matches the creative behavior of {@link ItemUsage#exchangeStack}.
|
||||
*/
|
||||
static ContainerItemContext forCreativeInteraction(PlayerEntity player, ItemStack interactingStack) {
|
||||
return new CreativeInteractionContainerItemContext(ItemVariant.of(interactingStack), interactingStack.getCount(), player);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a context for the passed player's hand.
|
||||
*/
|
||||
|
@ -132,12 +147,36 @@ public interface ContainerItemContext {
|
|||
}
|
||||
|
||||
/**
|
||||
* Return a context that can accept anything, and will accept (and destroy) any overflow items, with some initial content.
|
||||
* Return a context that always has some content, and will accept (and destroy) any overflow items.
|
||||
* This can typically be used to check if a stack provides an API, or simulate operations on the returned API,
|
||||
* for example to simulate how much fluid could be extracted from the stack.
|
||||
*
|
||||
* <p>Note that the stack can never be mutated by this function: its contents are copied directly.
|
||||
*/
|
||||
static ContainerItemContext withConstant(ItemStack constantContent) {
|
||||
return withConstant(ItemVariant.of(constantContent), constantContent.getCount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a context that always has some content, and will accept (and destroy) any overflow items.
|
||||
* This can typically be used to check if a stack provides an API, or simulate operations on the returned API,
|
||||
* for example to simulate how much fluid could be extracted from the variant and amount.
|
||||
*/
|
||||
static ContainerItemContext withConstant(ItemVariant constantVariant, long constantAmount) {
|
||||
StoragePreconditions.notNegative(constantAmount);
|
||||
return new ConstantContainerItemContext(constantVariant, constantAmount);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a context that can accept anything, and will accept (and destroy) any overflow items, with some initial content.
|
||||
* This can typically be used to check if a stack provides an API, or simulate operations on the returned API,
|
||||
* for example to simulate how much fluid could be extracted from the stack.
|
||||
*
|
||||
* <p>Note that the stack can never be mutated by this function: its contents are copied directly.
|
||||
*
|
||||
* @deprecated Use {@link #withConstant(ItemStack)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
static ContainerItemContext withInitial(ItemStack initialContent) {
|
||||
return withInitial(ItemVariant.of(initialContent), initialContent.getCount());
|
||||
}
|
||||
|
@ -146,7 +185,10 @@ public interface ContainerItemContext {
|
|||
* Return a context that can accept anything, and will accept (and destroy) any overflow items, with some initial variant and amount.
|
||||
* This can typically be used to check if a variant provides an API, or simulate operations on the returned API,
|
||||
* for example to simulate how much fluid could be extracted from the variant and amount.
|
||||
*
|
||||
* @deprecated Use {@link #withConstant(ItemVariant, long)} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
static ContainerItemContext withInitial(ItemVariant initialVariant, long initialAmount) {
|
||||
StoragePreconditions.notNegative(initialAmount);
|
||||
return new InitialContentsContainerItemContext(initialVariant, initialAmount);
|
||||
|
|
|
@ -48,7 +48,8 @@ public final class FluidStorageUtil {
|
|||
* Then, it tries to fill that item from the storage. If that fails, it tries to fill the storage from that item.
|
||||
*
|
||||
* <p>Only up to one fluid variant will be moved, and the corresponding emptying/filling sound will be played.
|
||||
* In creative mode, the player's inventory will not be modified.
|
||||
* In creative mode, the original container item is not modified,
|
||||
* and the player's inventory will additionally receive a copy of the modified container, if it doesn't have it yet.
|
||||
*
|
||||
* @param storage The storage that the player is interacting with.
|
||||
* @param player The player.
|
||||
|
|
|
@ -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.impl.transfer.context;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
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.SingleSlotStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
public class ConstantContainerItemContext implements ContainerItemContext {
|
||||
private final SingleVariantStorage<ItemVariant> backingSlot = new SingleVariantStorage<>() {
|
||||
@Override
|
||||
protected ItemVariant getBlankVariant() {
|
||||
return ItemVariant.blank();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getCapacity(ItemVariant variant) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insert(ItemVariant insertedVariant, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notBlankNotNegative(insertedVariant, maxAmount);
|
||||
|
||||
// Pretend we can't insert anything to route every insertion through insertOverflow.
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long extract(ItemVariant extractedVariant, long maxAmount, TransactionContext transaction) {
|
||||
StoragePreconditions.notBlankNotNegative(extractedVariant, maxAmount);
|
||||
|
||||
// Pretend we can extract anything, but never actually do it.
|
||||
return maxAmount;
|
||||
}
|
||||
};
|
||||
|
||||
public ConstantContainerItemContext(ItemVariant initialVariant, long initialAmount) {
|
||||
backingSlot.variant = initialVariant;
|
||||
backingSlot.amount = initialAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SingleSlotStorage<ItemVariant> getMainSlot() {
|
||||
return backingSlot;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insertOverflow(ItemVariant itemVariant, long maxAmount, TransactionContext transactionContext) {
|
||||
StoragePreconditions.notBlankNotNegative(itemVariant, maxAmount);
|
||||
// Always allow anything to be inserted.
|
||||
return maxAmount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SingleSlotStorage<ItemVariant>> getAdditionalSlots() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.context;
|
||||
|
||||
import net.minecraft.entity.player.PlayerEntity;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.PlayerInventoryStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.StoragePreconditions;
|
||||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
public class CreativeInteractionContainerItemContext extends ConstantContainerItemContext {
|
||||
private final PlayerInventoryStorage playerInventory;
|
||||
|
||||
public CreativeInteractionContainerItemContext(ItemVariant initialVariant, long initialAmount, PlayerEntity player) {
|
||||
super(initialVariant, initialAmount);
|
||||
|
||||
this.playerInventory = PlayerInventoryStorage.of(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long insertOverflow(ItemVariant itemVariant, long maxAmount, TransactionContext transactionContext) {
|
||||
StoragePreconditions.notBlankNotNegative(itemVariant, maxAmount);
|
||||
|
||||
if (maxAmount > 0) {
|
||||
// Only add the item to the player inventory if it's not already in the inventory.
|
||||
boolean hasItem = false;
|
||||
|
||||
for (SingleSlotStorage<ItemVariant> slot : playerInventory.getSlots()) {
|
||||
if (slot.getResource().equals(itemVariant) && slot.getAmount() > 0) {
|
||||
hasItem = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasItem) {
|
||||
playerInventory.offer(itemVariant, 1, transactionContext);
|
||||
}
|
||||
}
|
||||
|
||||
// Insertion always succeeds from the POV of the context user.
|
||||
return maxAmount;
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleSlotStorage;
|
|||
import net.fabricmc.fabric.api.transfer.v1.storage.base.SingleVariantStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.transaction.TransactionContext;
|
||||
|
||||
@Deprecated(forRemoval = true)
|
||||
public class InitialContentsContainerItemContext implements ContainerItemContext {
|
||||
private final SingleVariantStorage<ItemVariant> backingSlot = new SingleVariantStorage<>() {
|
||||
@Override
|
||||
|
|
|
@ -16,11 +16,15 @@
|
|||
|
||||
package net.fabricmc.fabric.impl.transfer.item;
|
||||
|
||||
import net.minecraft.block.ChestBlock;
|
||||
import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
|
||||
import net.minecraft.block.entity.BrewingStandBlockEntity;
|
||||
import net.minecraft.block.entity.ChestBlockEntity;
|
||||
import net.minecraft.block.entity.ShulkerBoxBlockEntity;
|
||||
import net.minecraft.block.enums.ChestType;
|
||||
import net.minecraft.item.ItemStack;
|
||||
import net.minecraft.item.Items;
|
||||
import net.minecraft.util.math.BlockPos;
|
||||
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.base.SingleStackStorage;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.ItemVariant;
|
||||
|
@ -112,6 +116,15 @@ class InventorySlotWrapper extends SingleStackStorage {
|
|||
public void updateSnapshots(TransactionContext transaction) {
|
||||
storage.markDirtyParticipant.updateSnapshots(transaction);
|
||||
super.updateSnapshots(transaction);
|
||||
|
||||
// For chests: also schedule a markDirty call for the other half
|
||||
if (storage.inventory instanceof ChestBlockEntity chest && chest.getCachedState().get(ChestBlock.CHEST_TYPE) != ChestType.SINGLE) {
|
||||
BlockPos otherChestPos = chest.getPos().offset(ChestBlock.getFacing(chest.getCachedState()));
|
||||
|
||||
if (chest.getWorld().getBlockEntity(otherChestPos) instanceof ChestBlockEntity otherChest) {
|
||||
((InventoryStorageImpl) InventoryStorageImpl.of(otherChest, null)).markDirtyParticipant.updateSnapshots(transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
|
||||
package net.fabricmc.fabric.test.transfer.gametests;
|
||||
|
||||
import org.apache.commons.lang3.mutable.MutableInt;
|
||||
|
||||
import net.minecraft.block.Blocks;
|
||||
import net.minecraft.block.ComparatorBlock;
|
||||
import net.minecraft.block.entity.BrewingStandBlockEntity;
|
||||
|
@ -34,7 +36,9 @@ import net.minecraft.world.World;
|
|||
|
||||
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
|
||||
import net.fabricmc.fabric.api.transfer.v1.item.InventoryStorage;
|
||||
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;
|
||||
import net.fabricmc.fabric.test.transfer.mixin.AbstractFurnaceBlockEntityAccessor;
|
||||
|
||||
|
@ -185,4 +189,45 @@ public class VanillaStorageTests {
|
|||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Regression test for <a href="https://github.com/FabricMC/fabric/issues/2810">double chest wrapper only updating modified halves</a>.
|
||||
*/
|
||||
@GameTest(templateName = "fabric-transfer-api-v1-testmod:double_chest_comparators")
|
||||
public void testDoubleChestComparator(TestContext context) {
|
||||
BlockPos chestPos = new BlockPos(2, 2, 2);
|
||||
Storage<ItemVariant> storage = ItemStorage.SIDED.find(context.getWorld(), context.getAbsolutePos(chestPos), Direction.UP);
|
||||
assertTrue(context, storage != null, "Storage must not be null");
|
||||
|
||||
// Insert one item
|
||||
try (Transaction tx = Transaction.openOuter()) {
|
||||
assertTrue(context, storage.insert(ItemVariant.of(Items.DIAMOND), 1, tx) == 1, "Diamond should have been inserted");
|
||||
tx.commit();
|
||||
}
|
||||
|
||||
// Check that an update is queued for every single comparator
|
||||
MutableInt comparatorCount = new MutableInt();
|
||||
|
||||
context.forEachRelativePos(relativePos -> {
|
||||
if (context.getBlockState(relativePos).getBlock() != Blocks.COMPARATOR) {
|
||||
return;
|
||||
}
|
||||
|
||||
comparatorCount.increment();
|
||||
|
||||
if (!context.getWorld().getBlockTickScheduler().isQueued(context.getAbsolutePos(relativePos), Blocks.COMPARATOR)) {
|
||||
throw new GameTestException("Comparator at " + relativePos + " should have an update scheduled");
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(context, comparatorCount.intValue() == 6, "Expected exactly 6 comparators");
|
||||
|
||||
context.complete();
|
||||
}
|
||||
|
||||
private static void assertTrue(TestContext context, boolean condition, String message) {
|
||||
if (!condition) {
|
||||
context.throwGameTestException(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class FluidItemTests {
|
|||
testSimpleContentsQuery();
|
||||
|
||||
// Ensure this doesn't throw an error due to the empty stack.
|
||||
assertEquals(null, ContainerItemContext.withInitial(ItemStack.EMPTY).find(FluidStorage.ITEM));
|
||||
assertEquals(null, ContainerItemContext.withConstant(ItemStack.EMPTY).find(FluidStorage.ITEM));
|
||||
}
|
||||
|
||||
private static void testFluidItemApi() {
|
||||
|
@ -174,7 +174,7 @@ class FluidItemTests {
|
|||
assertEquals(
|
||||
new ResourceAmount<>(FluidVariant.of(Fluids.WATER), BUCKET),
|
||||
StorageUtil.findExtractableContent(
|
||||
ContainerItemContext.withInitial(new ItemStack(Items.WATER_BUCKET)).find(FluidStorage.ITEM),
|
||||
ContainerItemContext.withConstant(new ItemStack(Items.WATER_BUCKET)).find(FluidStorage.ITEM),
|
||||
null
|
||||
)
|
||||
);
|
||||
|
@ -182,7 +182,7 @@ class FluidItemTests {
|
|||
assertEquals(
|
||||
null,
|
||||
StorageUtil.findExtractableContent(
|
||||
ContainerItemContext.withInitial(new ItemStack(Items.WATER_BUCKET)).find(FluidStorage.ITEM),
|
||||
ContainerItemContext.withConstant(new ItemStack(Items.WATER_BUCKET)).find(FluidStorage.ITEM),
|
||||
FluidVariant::hasNbt, // Only allow NBT -> won't match anything.
|
||||
null
|
||||
)
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
DataVersion: 3218,
|
||||
size: [5, 2, 6],
|
||||
data: [
|
||||
{pos: [0, 0, 0], state: "minecraft:air"},
|
||||
{pos: [0, 0, 1], state: "minecraft:air"},
|
||||
{pos: [0, 0, 2], state: "minecraft:stone"},
|
||||
{pos: [0, 0, 3], state: "minecraft:stone"},
|
||||
{pos: [0, 0, 4], state: "minecraft:air"},
|
||||
{pos: [0, 0, 5], state: "minecraft:air"},
|
||||
{pos: [1, 0, 0], state: "minecraft:air"},
|
||||
{pos: [1, 0, 1], state: "minecraft:air"},
|
||||
{pos: [1, 0, 2], state: "minecraft:air"},
|
||||
{pos: [1, 0, 3], state: "minecraft:air"},
|
||||
{pos: [1, 0, 4], state: "minecraft:air"},
|
||||
{pos: [1, 0, 5], state: "minecraft:air"},
|
||||
{pos: [2, 0, 0], state: "minecraft:stone"},
|
||||
{pos: [2, 0, 1], state: "minecraft:air"},
|
||||
{pos: [2, 0, 2], state: "minecraft:air"},
|
||||
{pos: [2, 0, 3], state: "minecraft:air"},
|
||||
{pos: [2, 0, 4], state: "minecraft:air"},
|
||||
{pos: [2, 0, 5], state: "minecraft:stone"},
|
||||
{pos: [3, 0, 0], state: "minecraft:air"},
|
||||
{pos: [3, 0, 1], state: "minecraft:air"},
|
||||
{pos: [3, 0, 2], state: "minecraft:air"},
|
||||
{pos: [3, 0, 3], state: "minecraft:air"},
|
||||
{pos: [3, 0, 4], state: "minecraft:air"},
|
||||
{pos: [3, 0, 5], state: "minecraft:air"},
|
||||
{pos: [4, 0, 0], state: "minecraft:air"},
|
||||
{pos: [4, 0, 1], state: "minecraft:air"},
|
||||
{pos: [4, 0, 2], state: "minecraft:stone"},
|
||||
{pos: [4, 0, 3], state: "minecraft:stone"},
|
||||
{pos: [4, 0, 4], state: "minecraft:air"},
|
||||
{pos: [4, 0, 5], state: "minecraft:air"},
|
||||
{pos: [0, 1, 0], state: "minecraft:air"},
|
||||
{pos: [0, 1, 1], state: "minecraft:air"},
|
||||
{pos: [0, 1, 2], state: "minecraft:comparator{facing:east,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
|
||||
{pos: [0, 1, 3], state: "minecraft:comparator{facing:east,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
|
||||
{pos: [0, 1, 4], state: "minecraft:air"},
|
||||
{pos: [0, 1, 5], state: "minecraft:air"},
|
||||
{pos: [1, 1, 0], state: "minecraft:air"},
|
||||
{pos: [1, 1, 1], state: "minecraft:air"},
|
||||
{pos: [1, 1, 2], state: "minecraft:stone"},
|
||||
{pos: [1, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [1, 1, 4], state: "minecraft:air"},
|
||||
{pos: [1, 1, 5], state: "minecraft:air"},
|
||||
{pos: [2, 1, 0], state: "minecraft:comparator{facing:south,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
|
||||
{pos: [2, 1, 1], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 2], state: "minecraft:chest{facing:east,type:left,waterlogged:false}", nbt: {Items: [], id: "minecraft:chest"}},
|
||||
{pos: [2, 1, 3], state: "minecraft:chest{facing:east,type:right,waterlogged:false}", nbt: {Items: [], id: "minecraft:chest"}},
|
||||
{pos: [2, 1, 4], state: "minecraft:stone"},
|
||||
{pos: [2, 1, 5], state: "minecraft:comparator{facing:north,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
|
||||
{pos: [3, 1, 0], state: "minecraft:air"},
|
||||
{pos: [3, 1, 1], state: "minecraft:air"},
|
||||
{pos: [3, 1, 2], state: "minecraft:stone"},
|
||||
{pos: [3, 1, 3], state: "minecraft:stone"},
|
||||
{pos: [3, 1, 4], state: "minecraft:air"},
|
||||
{pos: [3, 1, 5], state: "minecraft:air"},
|
||||
{pos: [4, 1, 0], state: "minecraft:air"},
|
||||
{pos: [4, 1, 1], state: "minecraft:air"},
|
||||
{pos: [4, 1, 2], state: "minecraft:comparator{facing:west,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
|
||||
{pos: [4, 1, 3], state: "minecraft:comparator{facing:west,mode:compare,powered:false}", nbt: {OutputSignal: 0, id: "minecraft:comparator"}},
|
||||
{pos: [4, 1, 4], state: "minecraft:air"},
|
||||
{pos: [4, 1, 5], state: "minecraft:air"}
|
||||
],
|
||||
entities: [],
|
||||
palette: [
|
||||
"minecraft:stone",
|
||||
"minecraft:air",
|
||||
"minecraft:comparator{facing:east,mode:compare,powered:false}",
|
||||
"minecraft:comparator{facing:south,mode:compare,powered:false}",
|
||||
"minecraft:chest{facing:east,type:left,waterlogged:false}",
|
||||
"minecraft:chest{facing:east,type:right,waterlogged:false}",
|
||||
"minecraft:comparator{facing:north,mode:compare,powered:false}",
|
||||
"minecraft:comparator{facing:west,mode:compare,powered:false}"
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue