Support stack aware recipe remainders ()

* Support stack aware recipe remainders

* Fix checkstyle

* Remove all overwrites

* Add FabricItemStack and make RecipeRemainderHandler thread safe

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: apple502j <33279053+apple502j@users.noreply.github.com>

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: apple502j <33279053+apple502j@users.noreply.github.com>

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: apple502j <33279053+apple502j@users.noreply.github.com>

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: apple502j <33279053+apple502j@users.noreply.github.com>

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: apple502j <33279053+apple502j@users.noreply.github.com>

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/impl/item/RecipeRemainderHandler.java

Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>

* Remove hasRecipeRemainder, Update test mod and remove unneeded mixins

* Update fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java

Co-authored-by: Salvatore Peluso <info@devpelux.xyz>

* Avoid copying the ItemStack

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/mixin/item/AbstractFurnaceBlockEntityMixin.java

Co-authored-by: Salvatore Peluso <info@devpelux.xyz>

* Sneakily change duplicate keybinding to a less used key

* make everything thread safe and improve AbstractFurnaceBlockEntityMixin

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItemStack.java

Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: Salvatore Peluso <info@devpelux.xyz>

* clear thread local and change field prefix

* forgot the allow

* Update fabric-item-api-v1/src/main/java/net/fabricmc/fabric/api/item/v1/FabricItem.java

Co-authored-by: Salvatore Peluso <info@devpelux.xyz>

* Update fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item/CustomDamageTest.java

Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>

* Add FurnaceGameTest

* Change test keybind back to LShift

* Fix brewing stand remainder and fix nitpicks

* add code example to remainder javadoc

* Fixed and reformatted docs, changed recipe mixin behavior to store the remainder stack instead of the original stack, refactoring.

* Added gametests for brewing stand and recipe mixins, fixed furnace gametest compairing stacks with themselves.

* Use (0,1,0) position for game tests

* Review changes

Co-authored-by: apple502j <33279053+apple502j@users.noreply.github.com>
Co-authored-by: Technici4n <13494793+Technici4n@users.noreply.github.com>
Co-authored-by: Salvatore Peluso <info@devpelux.xyz>
Co-authored-by: modmuss50 <modmuss50@gmail.com>
(cherry picked from commit fa140d5976)
This commit is contained in:
AlphaMode 2022-11-20 07:25:23 -06:00 committed by modmuss50
parent 8d7ffdee44
commit d8cf4e5a10
18 changed files with 774 additions and 6 deletions

View file

@ -4,3 +4,7 @@ version = getSubprojectVersion(project)
moduleDependencies(project, [
'fabric-api-base'
])
dependencies {
testmodImplementation project(path: ':fabric-content-registries-v0', configuration: 'namedElements')
}

View file

@ -91,4 +91,36 @@ public interface FabricItem {
default boolean isSuitableFor(ItemStack stack, BlockState state) {
return ((Item) this).isSuitableFor(state);
}
/**
* Returns a leftover item stack after {@code stack} is consumed in a recipe.
* (This is also known as "recipe remainder".)
* For example, using a lava bucket in a furnace as fuel will leave an empty bucket.
*
* <p>Here is an example for a recipe remainder that increments the item's damage.
*
* <pre>
* if (stack.getDamage() < stack.getMaxDamage() - 1) {
* ItemStack moreDamaged = stack.copy();
* moreDamaged.setDamage(stack.getDamage() + 1);
* return moreDamaged;
* }
*
* return ItemStack.EMPTY;
* </pre>
*
*
* <p>This is a stack-aware version of {@link Item#getRecipeRemainder()}.
*
* <p>Note that simple item remainders can also be set via {@link Item.Settings#recipeRemainder(Item)}.
*
* <p>If you want to get a remainder for a stack,
* is recommended to use the stack version of this method: {@link FabricItemStack#getRecipeRemainder()}.
*
* @param stack the consumed {@link ItemStack}
* @return the leftover item stack
*/
default ItemStack getRecipeRemainder(ItemStack stack) {
return ((Item) this).hasRecipeRemainder() ? ((Item) this).getRecipeRemainder().getDefaultStack() : ItemStack.EMPTY;
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.api.item.v1;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
/*
* Fabric-provided extensions for {@link ItemStack}.
* This interface is automatically implemented on all item stacks via Mixin and interface injection.
*/
public interface FabricItemStack {
/**
* Return a leftover item for use in recipes.
*
* <p>See {@link FabricItem#getRecipeRemainder(ItemStack)} for a more in depth description.
*
* <p>Stack-aware version of {@link Item#getRecipeRemainder()}.
*
* @return the leftover item
*/
default ItemStack getRecipeRemainder() {
return ((ItemStack) this).getItem().getRecipeRemainder((ItemStack) this);
}
}

View file

@ -0,0 +1,26 @@
/*
* 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.item;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.item.ItemStack;
@ApiStatus.Internal
public class RecipeRemainderHandler {
public static final ThreadLocal<ItemStack> REMAINDER_STACK = new ThreadLocal<>();
}

View file

@ -0,0 +1,50 @@
/*
* 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.item;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.BlockState;
import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.recipe.Recipe;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
@Mixin(AbstractFurnaceBlockEntity.class)
public abstract class AbstractFurnaceBlockEntityMixin {
@Unique
private static final ThreadLocal<ItemStack> REMAINDER_STACK = new ThreadLocal<>();
@Inject(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;getItem()Lnet/minecraft/item/Item;"), locals = LocalCapture.CAPTURE_FAILHARD, allow = 1)
private static void getStackRemainder(World world, BlockPos pos, BlockState state, AbstractFurnaceBlockEntity blockEntity, CallbackInfo ci, boolean bl, boolean bl2, ItemStack itemStack, Recipe recipe, int i) {
REMAINDER_STACK.set(itemStack.getRecipeRemainder());
}
@ModifyArg(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/collection/DefaultedList;set(ILjava/lang/Object;)Ljava/lang/Object;"), index = 1, allow = 1)
private static <E> E setStackRemainder(E element) {
E remainder = (E) REMAINDER_STACK.get();
REMAINDER_STACK.remove();
return remainder;
}
}

View file

@ -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.mixin.item;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.block.entity.BrewingStandBlockEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
@Mixin(BrewingStandBlockEntity.class)
public class BrewingStandBlockEntityMixin {
@Unique
private static final ThreadLocal<ItemStack> REMAINDER_STACK = new ThreadLocal<>();
@Inject(method = "craft", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;decrement(I)V"), locals = LocalCapture.CAPTURE_FAILHARD)
private static void captureItemStack(World world, BlockPos pos, DefaultedList<ItemStack> slots, CallbackInfo ci, ItemStack itemStack) {
REMAINDER_STACK.set(itemStack.getRecipeRemainder());
}
@Redirect(method = "craft", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;hasRecipeRemainder()Z"))
private static boolean hasStackRecipeRemainder(Item instance) {
return !REMAINDER_STACK.get().isEmpty();
}
/**
* Injected after the {@link Item#getRecipeRemainder} to replace the old remainder with are new one.
*/
@ModifyVariable(method = "craft", at = @At(value = "STORE"), index = 4)
private static ItemStack createStackRecipeRemainder(ItemStack old) {
ItemStack remainder = REMAINDER_STACK.get();
REMAINDER_STACK.remove();
return remainder;
}
}

View file

@ -37,12 +37,13 @@ import net.minecraft.entity.attribute.EntityAttributeModifier;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.fabricmc.fabric.api.item.v1.FabricItemStack;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.ModifyItemAttributeModifiersCallback;
import net.fabricmc.fabric.impl.item.ItemExtensions;
@Mixin(ItemStack.class)
public abstract class ItemStackMixin {
public abstract class ItemStackMixin implements FabricItemStack {
@Shadow public abstract Item getItem();
@Unique

View file

@ -0,0 +1,52 @@
/*
* 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.item;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.inventory.Inventory;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.recipe.Recipe;
import net.minecraft.util.collection.DefaultedList;
import net.fabricmc.fabric.impl.item.RecipeRemainderHandler;
@Mixin(Recipe.class)
public interface RecipeMixin<C extends Inventory> {
@Inject(method = "getRemainder", at = @At(value = "INVOKE", target = "Lnet/minecraft/inventory/Inventory;getStack(I)Lnet/minecraft/item/ItemStack;"), locals = LocalCapture.CAPTURE_FAILHARD)
default void captureStack(C inventory, CallbackInfoReturnable<DefaultedList<ItemStack>> cir, DefaultedList<ItemStack> defaultedList, int i) {
RecipeRemainderHandler.REMAINDER_STACK.set(inventory.getStack(i).getRecipeRemainder());
}
@Redirect(method = "getRemainder", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/Item;hasRecipeRemainder()Z"))
private boolean hasStackRemainder(Item instance) {
return !RecipeRemainderHandler.REMAINDER_STACK.get().isEmpty();
}
@Redirect(method = "getRemainder", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/collection/DefaultedList;set(ILjava/lang/Object;)Ljava/lang/Object;"))
private Object getStackRemainder(DefaultedList<ItemStack> inventory, int index, Object element) {
Object remainder = inventory.set(index, RecipeRemainderHandler.REMAINDER_STACK.get());
RecipeRemainderHandler.REMAINDER_STACK.remove();
return remainder;
}
}

View file

@ -3,10 +3,13 @@
"package": "net.fabricmc.fabric.mixin.item",
"compatibilityLevel": "JAVA_16",
"mixins": [
"ItemStackMixin",
"AbstractFurnaceBlockEntityMixin",
"ArmorItemMixin",
"BrewingStandBlockEntityMixin",
"ItemMixin",
"ItemStackMixin",
"LivingEntityMixin",
"ArmorItemMixin"
"RecipeMixin"
],
"client": [
"client.ClientPlayerInteractionManagerMixin",

View file

@ -26,7 +26,8 @@
"custom": {
"fabric-api:module-lifecycle": "stable",
"loom:injected_interfaces": {
"net/minecraft/class_1792": ["net/fabricmc/fabric/api/item/v1/FabricItem"]
"net/minecraft/class_1792": ["net/fabricmc/fabric/api/item/v1/FabricItem"],
"net/minecraft/class_1799": ["net/fabricmc/fabric/api/item/v1/FabricItemStack"]
}
}
}

View file

@ -16,10 +16,12 @@
package net.fabricmc.fabric.test.item;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.PickaxeItem;
import net.minecraft.item.ToolMaterials;
import net.minecraft.nbt.NbtCompound;
import net.minecraft.potion.Potions;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
@ -27,11 +29,17 @@ import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
import net.fabricmc.fabric.api.registry.FuelRegistry;
import net.fabricmc.fabric.test.item.mixin.BrewingRecipeRegistryAccessor;
public class CustomDamageTest implements ModInitializer {
public static final Item WEIRD_PICK = new WeirdPick();
@Override
public void onInitialize() {
Registry.register(Registry.ITEM, new Identifier("fabric-item-api-v1-testmod", "weird_pickaxe"), new WeirdPick());
Registry.register(Registry.ITEM, new Identifier("fabric-item-api-v1-testmod", "weird_pickaxe"), WEIRD_PICK);
FuelRegistry.INSTANCE.add(WEIRD_PICK, 200);
BrewingRecipeRegistryAccessor.callRegisterPotionRecipe(Potions.WATER, WEIRD_PICK, Potions.AWKWARD);
}
public static final CustomDamageHandler WEIRD_DAMAGE_HANDLER = (stack, amount, entity, breakCallback) -> {
@ -55,5 +63,17 @@ public class CustomDamageTest implements ModInitializer {
int v = stack.getOrCreateNbt().getInt("weird");
return super.getName(stack).shallowCopy().append(" (Weird Value: " + v + ")");
}
@Override
public ItemStack getRecipeRemainder(ItemStack stack) {
if (stack.getDamage() < stack.getMaxDamage() - 1) {
ItemStack moreDamaged = stack.copy();
moreDamaged.setCount(1);
moreDamaged.setDamage(stack.getDamage() + 1);
return moreDamaged;
}
return ItemStack.EMPTY;
}
}
}

View file

@ -0,0 +1,149 @@
/*
* 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.item.gametest;
import java.util.Objects;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.BrewingStandBlockEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.potion.PotionUtil;
import net.minecraft.potion.Potions;
import net.minecraft.test.GameTest;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.fabric.test.item.CustomDamageTest;
public class BrewingStandGameTest implements FabricGameTest {
private static final int BREWING_TIME = 800;
private static final BlockPos POS = new BlockPos(0, 1, 0);
@GameTest(structureName = EMPTY_STRUCTURE)
public void basicBrewing(TestContext context) {
context.setBlockState(POS, Blocks.BREWING_STAND);
BrewingStandBlockEntity blockEntity = (BrewingStandBlockEntity) Objects.requireNonNull(context.getBlockEntity(POS));
loadFuel(blockEntity, context);
prepareForBrewing(blockEntity, new ItemStack(Items.NETHER_WART, 8),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.WATER));
brew(blockEntity, context);
assertInventory(blockEntity, "Testing vanilla brewing.",
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
new ItemStack(Items.NETHER_WART, 7),
ItemStack.EMPTY);
context.complete();
}
@GameTest(structureName = EMPTY_STRUCTURE)
public void vanillaRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.BREWING_STAND);
BrewingStandBlockEntity blockEntity = (BrewingStandBlockEntity) Objects.requireNonNull(context.getBlockEntity(POS));
loadFuel(blockEntity, context);
prepareForBrewing(blockEntity, new ItemStack(Items.DRAGON_BREATH),
PotionUtil.setPotion(new ItemStack(Items.SPLASH_POTION), Potions.AWKWARD));
brew(blockEntity, context);
assertInventory(blockEntity, "Testing vanilla brewing recipe remainder.",
PotionUtil.setPotion(new ItemStack(Items.LINGERING_POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.LINGERING_POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.LINGERING_POTION), Potions.AWKWARD),
new ItemStack(Items.GLASS_BOTTLE),
ItemStack.EMPTY);
context.complete();
}
@GameTest(structureName = EMPTY_STRUCTURE)
public void fabricRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.BREWING_STAND);
BrewingStandBlockEntity blockEntity = (BrewingStandBlockEntity) Objects.requireNonNull(context.getBlockEntity(POS));
loadFuel(blockEntity, context);
prepareForBrewing(blockEntity, new ItemStack(CustomDamageTest.WEIRD_PICK),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.WATER));
brew(blockEntity, context);
assertInventory(blockEntity, "Testing fabric brewing recipe remainder.",
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
RecipeGameTest.withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 1),
ItemStack.EMPTY);
prepareForBrewing(blockEntity, RecipeGameTest.withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 10),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.WATER));
brew(blockEntity, context);
assertInventory(blockEntity, "Testing fabric brewing recipe remainder.",
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
RecipeGameTest.withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 11),
ItemStack.EMPTY);
prepareForBrewing(blockEntity, RecipeGameTest.withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 31),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.WATER));
brew(blockEntity, context);
assertInventory(blockEntity, "Testing fabric brewing recipe remainder.",
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
PotionUtil.setPotion(new ItemStack(Items.POTION), Potions.AWKWARD),
ItemStack.EMPTY,
ItemStack.EMPTY);
context.complete();
}
private void prepareForBrewing(BrewingStandBlockEntity blockEntity, ItemStack ingredient, ItemStack potion) {
blockEntity.setStack(0, potion.copy());
blockEntity.setStack(1, potion.copy());
blockEntity.setStack(2, potion.copy());
blockEntity.setStack(3, ingredient);
}
private void assertInventory(BrewingStandBlockEntity blockEntity, String extraErrorInfo, ItemStack... stacks) {
for (int i = 0; i < stacks.length; i++) {
ItemStack currentStack = blockEntity.getStack(i);
ItemStack expectedStack = stacks[i];
RecipeGameTest.assertStacks(currentStack, expectedStack, extraErrorInfo);
}
}
private void loadFuel(BrewingStandBlockEntity blockEntity, TestContext context) {
blockEntity.setStack(4, new ItemStack(Items.BLAZE_POWDER));
BrewingStandBlockEntity.tick(context.getWorld(), POS, context.getBlockState(POS), blockEntity);
}
private void brew(BrewingStandBlockEntity blockEntity, TestContext context) {
for (int i = 0; i < BREWING_TIME; i++) {
BrewingStandBlockEntity.tick(context.getWorld(), POS, context.getBlockState(POS), blockEntity);
}
}
}

View file

@ -0,0 +1,122 @@
/*
* 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.item.gametest;
import java.util.Objects;
import net.minecraft.block.Blocks;
import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.block.entity.FurnaceBlockEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.test.GameTest;
import net.minecraft.test.TestContext;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.fabric.test.item.CustomDamageTest;
public class FurnaceGameTest implements FabricGameTest {
private static final int COOK_TIME = 200;
private static final BlockPos POS = new BlockPos(0, 1, 0);
@GameTest(structureName = EMPTY_STRUCTURE)
public void basicSmelt(TestContext context) {
context.setBlockState(POS, Blocks.FURNACE);
FurnaceBlockEntity blockEntity = (FurnaceBlockEntity) Objects.requireNonNull(context.getBlockEntity(POS));
setInputs(blockEntity, new ItemStack(Blocks.COBBLESTONE, 8), new ItemStack(Items.COAL, 2));
cook(blockEntity, context, 1);
assertInventory(blockEntity, "Testing vanilla smelting.",
new ItemStack(Blocks.COBBLESTONE, 7),
new ItemStack(Items.COAL, 1),
new ItemStack(Blocks.STONE, 1));
cook(blockEntity, context, 7);
assertInventory(blockEntity, "Testing vanilla smelting.",
ItemStack.EMPTY,
new ItemStack(Items.COAL, 1),
new ItemStack(Blocks.STONE, 8));
context.complete();
}
@GameTest(structureName = EMPTY_STRUCTURE)
public void vanillaRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.FURNACE);
FurnaceBlockEntity blockEntity = (FurnaceBlockEntity) Objects.requireNonNull(context.getBlockEntity(POS));
setInputs(blockEntity, new ItemStack(Blocks.COBBLESTONE, 64), new ItemStack(Items.LAVA_BUCKET));
cook(blockEntity, context, 64);
assertInventory(blockEntity, "Testing vanilla smelting recipe remainder.",
ItemStack.EMPTY,
new ItemStack(Items.BUCKET),
new ItemStack(Blocks.STONE, 64));
context.complete();
}
@GameTest(structureName = EMPTY_STRUCTURE)
public void fabricRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.FURNACE);
FurnaceBlockEntity blockEntity = (FurnaceBlockEntity) Objects.requireNonNull(context.getBlockEntity(POS));
setInputs(blockEntity, new ItemStack(Blocks.COBBLESTONE, 32), new ItemStack(CustomDamageTest.WEIRD_PICK));
cook(blockEntity, context, 1);
assertInventory(blockEntity, "Testing fabric smelting recipe remainder.",
new ItemStack(Blocks.COBBLESTONE, 31),
RecipeGameTest.withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 1),
new ItemStack(Blocks.STONE, 1));
cook(blockEntity, context, 30);
assertInventory(blockEntity, "Testing fabric smelting recipe remainder.",
new ItemStack(Blocks.COBBLESTONE, 1),
RecipeGameTest.withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 31),
new ItemStack(Blocks.STONE, 31));
cook(blockEntity, context, 1);
assertInventory(blockEntity, "Testing fabric smelting recipe remainder.",
ItemStack.EMPTY,
ItemStack.EMPTY,
new ItemStack(Blocks.STONE, 32));
context.complete();
}
private void setInputs(FurnaceBlockEntity blockEntity, ItemStack ingredient, ItemStack fuel) {
blockEntity.setStack(0, ingredient);
blockEntity.setStack(1, fuel);
}
private void assertInventory(FurnaceBlockEntity blockEntity, String extraErrorInfo, ItemStack... stacks) {
for (int i = 0; i < stacks.length; i++) {
ItemStack currentStack = blockEntity.getStack(i);
ItemStack expectedStack = stacks[i];
RecipeGameTest.assertStacks(currentStack, expectedStack, extraErrorInfo);
}
}
private void cook(FurnaceBlockEntity blockEntity, TestContext context, int items) {
for (int i = 0; i < COOK_TIME * items; i++) {
AbstractFurnaceBlockEntity.tick(context.getWorld(), POS, context.getBlockState(POS), blockEntity);
}
}
}

View file

@ -0,0 +1,144 @@
/*
* 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.item.gametest;
import net.minecraft.inventory.SimpleInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.recipe.Recipe;
import net.minecraft.recipe.RecipeSerializer;
import net.minecraft.recipe.RecipeType;
import net.minecraft.test.GameTest;
import net.minecraft.test.GameTestException;
import net.minecraft.test.TestContext;
import net.minecraft.util.Identifier;
import net.minecraft.util.collection.DefaultedList;
import net.minecraft.world.World;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.fabric.test.item.CustomDamageTest;
public class RecipeGameTest implements FabricGameTest {
@GameTest(structureName = EMPTY_STRUCTURE)
public void vanillaRemainderTest(TestContext context) {
Recipe<SimpleInventory> testRecipe = createTestingRecipeInstance();
SimpleInventory inventory = new SimpleInventory(
new ItemStack(Items.WATER_BUCKET),
new ItemStack(Items.DIAMOND));
DefaultedList<ItemStack> remainderList = testRecipe.getRemainder(inventory);
assertStackList(remainderList, "Testing vanilla recipe remainder.",
new ItemStack(Items.BUCKET),
ItemStack.EMPTY);
context.complete();
}
@GameTest(structureName = EMPTY_STRUCTURE)
public void fabricRemainderTest(TestContext context) {
Recipe<SimpleInventory> testRecipe = createTestingRecipeInstance();
SimpleInventory inventory = new SimpleInventory(
new ItemStack(CustomDamageTest.WEIRD_PICK),
withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 10),
withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 31),
new ItemStack(Items.DIAMOND));
DefaultedList<ItemStack> remainderList = testRecipe.getRemainder(inventory);
assertStackList(remainderList, "Testing fabric recipe remainder.",
withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 1),
withDamage(new ItemStack(CustomDamageTest.WEIRD_PICK), 11),
ItemStack.EMPTY,
ItemStack.EMPTY);
context.complete();
}
private Recipe<SimpleInventory> createTestingRecipeInstance() {
return new Recipe<>() {
@Override
public boolean matches(SimpleInventory inventory, World world) {
return true;
}
@Override
public ItemStack craft(SimpleInventory inventory) {
return null;
}
@Override
public boolean fits(int width, int height) {
return true;
}
@Override
public ItemStack getOutput() {
return null;
}
@Override
public Identifier getId() {
return null;
}
@Override
public RecipeSerializer<?> getSerializer() {
return null;
}
@Override
public RecipeType<?> getType() {
return null;
}
};
}
private void assertStackList(DefaultedList<ItemStack> stackList, String extraErrorInfo, ItemStack... stacks) {
for (int i = 0; i < stackList.size(); i++) {
ItemStack currentStack = stackList.get(i);
ItemStack expectedStack = stacks[i];
assertStacks(currentStack, expectedStack, extraErrorInfo);
}
}
static void assertStacks(ItemStack currentStack, ItemStack expectedStack, String extraErrorInfo) {
if (currentStack.isEmpty() && expectedStack.isEmpty()) {
return;
}
if (!currentStack.isItemEqual(expectedStack)) {
throw new GameTestException("Item stacks dont match. " + extraErrorInfo);
}
if (currentStack.getCount() != expectedStack.getCount()) {
throw new GameTestException("Size doesnt match. " + extraErrorInfo);
}
if (!ItemStack.areNbtEqual(currentStack, expectedStack)) {
throw new GameTestException("Nbt doesnt match. " + extraErrorInfo);
}
}
static ItemStack withDamage(ItemStack stack, int damage) {
stack.setDamage(damage);
return stack;
}
}

View file

@ -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.test.item.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.item.Item;
import net.minecraft.potion.Potion;
import net.minecraft.recipe.BrewingRecipeRegistry;
@Mixin(BrewingRecipeRegistry.class)
public interface BrewingRecipeRegistryAccessor {
@Invoker
static void callRegisterPotionRecipe(Potion input, Item item, Potion output) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,15 @@
{
"type": "minecraft:crafting_shapeless",
"ingredients": [
{
"item": "minecraft:diamond_ore"
},
{
"item": "fabric-item-api-v1-testmod:weird_pickaxe"
}
],
"result": {
"item": "minecraft:diamond",
"count": 1
}
}

View file

@ -0,0 +1,11 @@
{
"required": true,
"package": "net.fabricmc.fabric.test.item.mixin",
"compatibilityLevel": "JAVA_16",
"mixins": [
"BrewingRecipeRegistryAccessor"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -18,6 +18,14 @@
],
"client": [
"net.fabricmc.fabric.test.item.client.TooltipTests"
],
"fabric-gametest" : [
"net.fabricmc.fabric.test.item.gametest.BrewingStandGameTest",
"net.fabricmc.fabric.test.item.gametest.FurnaceGameTest",
"net.fabricmc.fabric.test.item.gametest.RecipeGameTest"
]
}
},
"mixins": [
"fabric-item-api-tests-v1.mixins.json"
]
}