Add fuel registry events ()

* Add fuel registry events

* Allow removing individual items from fuel registry builders

* Expand fuel registry events test coverage

* Fix incorrect fuel registry documentation

* Review changes

* Use a context object

* Checkstyle

---------

Co-authored-by: modmuss50 <modmuss50@gmail.com>
This commit is contained in:
haykam821 2024-09-23 13:49:11 -04:00 committed by GitHub
parent 464f0e851d
commit d38542c645
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 340 additions and 6 deletions
fabric-content-registries-v0/src
main
java/net/fabricmc/fabric
api/registry
impl/content/registry
mixin/content/registry
resources
testmod
java/net/fabricmc/fabric/test/content/registry
resources
assets/fabric-content-registries-v0-testmod/lang
data
fabric-content-registries-v0-testmod/tags/item
minecraft/tags/item
fabric-item-api-v1/src/testmod/java/net/fabricmc/fabric/test/item

View file

@ -0,0 +1,99 @@
/*
* 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.registry;
import org.jetbrains.annotations.ApiStatus;
import net.minecraft.item.FuelRegistry;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.resource.featuretoggle.FeatureSet;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Contains events to aid in modifying fuels.
*/
@ApiStatus.NonExtendable
public interface FuelRegistryEvents {
/**
* An event that is called when the fuel registry is being built after vanilla fuels have been registered and before exclusions have been applied.
*/
Event<FuelRegistryEvents.BuildCallback> BUILD = EventFactory.createArrayBacked(FuelRegistryEvents.BuildCallback.class, listeners -> (builder, context) -> {
for (FuelRegistryEvents.BuildCallback listener : listeners) {
listener.build(builder, context);
}
});
/**
* An event that is called when the fuel registry is being built after vanilla exclusions have been applied.
*/
Event<FuelRegistryEvents.ExclusionsCallback> EXCLUSIONS = EventFactory.createArrayBacked(FuelRegistryEvents.ExclusionsCallback.class, listeners -> (builder, context) -> {
for (FuelRegistryEvents.ExclusionsCallback listener : listeners) {
listener.buildExclusions(builder, context);
}
});
@ApiStatus.NonExtendable
interface Context {
/**
* Get the base smelt time for the fuel, for furnaces this defaults to 200.
* @return the base smelt time
*/
int baseSmeltTime();
/**
* Get the {@link RegistryWrapper.WrapperLookup} for all registries.
* @return the registry lookup
*/
RegistryWrapper.WrapperLookup registries();
/**
* Get the currently enabled feature set.
* @return the {@link FeatureSet} instance
*/
FeatureSet enabledFeatures();
}
/**
* Use this event to register custom fuels.
*/
@FunctionalInterface
interface BuildCallback {
/**
* Called when the fuel registry is being built after vanilla fuels have been registered and before exclusions have been applied.
*
* @param builder the builder being used to construct a {@link FuelRegistry} instance
* @param context the context for the event
*/
void build(FuelRegistry.Builder builder, Context context);
}
/**
* Use this event to register custom fuels.
*/
@FunctionalInterface
interface ExclusionsCallback {
/**
* Called when the fuel registry is being built after vanilla exclusions have been applied.
*
* @param builder the builder being used to construct a {@link FuelRegistry} instance
* @param context the context for the event
*/
void buildExclusions(FuelRegistry.Builder builder, Context context);
}
}

View file

@ -0,0 +1,25 @@
/*
* 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.content.registry;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.resource.featuretoggle.FeatureSet;
import net.fabricmc.fabric.api.registry.FuelRegistryEvents;
public record FuelRegistryEventsContextImpl(RegistryWrapper.WrapperLookup registries, FeatureSet enabledFeatures, int baseSmeltTime) implements FuelRegistryEvents.Context {
}

View file

@ -0,0 +1,62 @@
/*
* 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.content.registry;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.llamalad7.mixinextras.sugar.Local;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import net.minecraft.item.FuelRegistry;
import net.minecraft.item.Item;
import net.minecraft.registry.RegistryWrapper;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.resource.featuretoggle.FeatureSet;
import net.fabricmc.fabric.api.registry.FuelRegistryEvents;
import net.fabricmc.fabric.impl.content.registry.FuelRegistryEventsContextImpl;
/**
* Implements the invocation of {@link FabricFuelRegistryBuilder} callbacks.
*/
@Mixin(FuelRegistry.class)
public abstract class FuelRegistryMixin {
/**
* Handles invoking both pre- and post-exclusion events.
*
* <p>Vanilla currently uses a single exclusion for non-flammable wood; if more builder calls for exclusions are added, this mixin method must be split accordingly.
*/
@WrapOperation(
method = "createDefault(Lnet/minecraft/registry/RegistryWrapper$WrapperLookup;Lnet/minecraft/resource/featuretoggle/FeatureSet;I)Lnet/minecraft/item/FuelRegistry;",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/item/FuelRegistry$Builder;remove(Lnet/minecraft/registry/tag/TagKey;)Lnet/minecraft/item/FuelRegistry$Builder;"
),
allow = 1
)
private static FuelRegistry.Builder build(FuelRegistry.Builder builder, TagKey<Item> tag, Operation<FuelRegistry.Builder> operation, @Local(argsOnly = true) RegistryWrapper.WrapperLookup registries, @Local(argsOnly = true) FeatureSet features, @Local(argsOnly = true) int baseSmeltTime) {
final var context = new FuelRegistryEventsContextImpl(registries, features, baseSmeltTime);
FuelRegistryEvents.BUILD.invoker().build(builder, context);
operation.call(builder, tag);
FuelRegistryEvents.EXCLUSIONS.invoker().buildExclusions(builder, context);
return builder;
}
}

View file

@ -7,6 +7,7 @@
"BrewingRecipeRegistryBuilderMixin",
"PathContextMixin",
"FarmerWorkTaskAccessor",
"FuelRegistryMixin",
"GiveGiftsToHeroTaskAccessor",
"GiveGiftsToHeroTaskMixin",
"HoeItemAccessor",

View file

@ -16,11 +16,16 @@
package net.fabricmc.fabric.test.content.registry;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.ComposterBlock;
import net.minecraft.block.HopperBlock;
import net.minecraft.block.entity.AbstractFurnaceBlockEntity;
import net.minecraft.block.entity.BrewingStandBlockEntity;
import net.minecraft.block.entity.HopperBlockEntity;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.PotionContentsComponent;
import net.minecraft.entity.player.PlayerEntity;
@ -31,6 +36,7 @@ import net.minecraft.test.GameTest;
import net.minecraft.test.TestContext;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.math.Direction;
import net.minecraft.world.GameMode;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
@ -63,6 +69,87 @@ public class ContentRegistryGameTest {
context.complete();
}
private void smelt(TestContext context, ItemStack fuelStack, BiConsumer<AbstractFurnaceBlockEntity, HopperBlockEntity> callback) {
// Create a furnace to simulate smelting in
// A blast furnace will smelt twice as fast, so it is used here
var furnacePos = new BlockPos(0, 1, 0);
BlockState furnaceState = Blocks.BLAST_FURNACE.getDefaultState();
context.setBlockState(furnacePos, furnaceState);
if (!(context.getBlockEntity(furnacePos) instanceof AbstractFurnaceBlockEntity furnace)) {
throw new AssertionError("Furnace was not placed");
}
// Create a hopper that attempts to insert fuel into the furnace
BlockPos hopperPos = furnacePos.east();
BlockState hopperState = Blocks.HOPPER.getDefaultState()
.with(HopperBlock.FACING, context.getRotation().rotate(Direction.WEST));
context.setBlockState(hopperPos, hopperState);
if (!(context.getBlockEntity(hopperPos) instanceof HopperBlockEntity hopper)) {
throw new AssertionError("Hopper was not placed");
}
// Insert the fuel into the hopper, which transfers it into the furnace
hopper.setStack(0, fuelStack.copy());
// Insert the item that should be smelted into the furnace
// Smelting a single item takes 200 fuel time
furnace.setStack(0, new ItemStack(Items.RAW_IRON, 1));
context.waitAndRun(105, () -> callback.accept(furnace, hopper));
}
private void smeltCompleted(TestContext context, ItemStack fuelStack) {
smelt(context, fuelStack, (furnace, hopper) -> {
context.assertTrue(hopper.isEmpty(), "fuel hopper should have been emptied");
context.assertTrue(furnace.getStack(0).isEmpty(), "furnace input slot should have been emptied");
context.assertTrue(furnace.getStack(0).isEmpty(), "furnace fuel slot should have been emptied");
context.assertTrue(ItemStack.areEqual(furnace.getStack(2), new ItemStack(Items.IRON_INGOT, 1)), "one iron ingot should have been smelted and placed into the furnace output slot");
context.complete();
});
}
private void smeltFailed(TestContext context, ItemStack fuelStack) {
smelt(context, fuelStack, (furnace, hopper) -> {
context.assertTrue(ItemStack.areEqual(hopper.getStack(0), fuelStack), "fuel hopper should not have been emptied");
context.assertTrue(ItemStack.areEqual(furnace.getStack(0), new ItemStack(Items.RAW_IRON, 1)), "furnace input slot should not have been emptied");
context.assertTrue(furnace.getStack(1).isEmpty(), "furnace fuel slot should not have been filled");
context.assertTrue(furnace.getStack(2).isEmpty(), "furnace output slot should not have been filled");
context.complete();
});
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
public void testSmeltingFuelIncludedByItem(TestContext context) {
// Item with 50 fuel time x4 = 200 fuel time
smeltCompleted(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_INCLUDED_BY_ITEM, 4));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
public void testSmeltingFuelIncludedByTag(TestContext context) {
// Item in tag with 100 fuel time x2 = 200 fuel time
smeltCompleted(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_INCLUDED_BY_TAG, 2));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
public void testSmeltingFuelExcludedByTag(TestContext context) {
// Item is in both the smelting fuels tag and the excluded smithing fuels tag
smeltFailed(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_EXCLUDED_BY_TAG));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE, tickLimit = 110)
public void testSmeltingFuelExcludedByVanillaTag(TestContext context) {
// Item is in both the smelting fuel tag and vanilla's excluded non-flammable wood tag
smeltFailed(context, new ItemStack(ContentRegistryTest.SMELTING_FUEL_EXCLUDED_BY_VANILLA_TAG));
}
@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
public void testStrippableBlockRegistry(TestContext context) {
BlockPos pos = new BlockPos(0, 1, 0);

View file

@ -39,6 +39,7 @@ import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.tag.BlockTags;
import net.minecraft.registry.tag.ItemTags;
import net.minecraft.registry.tag.TagKey;
import net.minecraft.resource.featuretoggle.FeatureFlags;
import net.minecraft.text.Text;
import net.minecraft.util.ActionResult;
@ -54,6 +55,7 @@ import net.fabricmc.fabric.api.registry.CompostingChanceRegistry;
import net.fabricmc.fabric.api.registry.FabricBrewingRecipeRegistryBuilder;
import net.fabricmc.fabric.api.registry.FlammableBlockRegistry;
import net.fabricmc.fabric.api.registry.FlattenableBlockRegistry;
import net.fabricmc.fabric.api.registry.FuelRegistryEvents;
import net.fabricmc.fabric.api.registry.LandPathNodeTypesRegistry;
import net.fabricmc.fabric.api.registry.OxidizableBlocksRegistry;
import net.fabricmc.fabric.api.registry.SculkSensorFrequencyRegistry;
@ -62,9 +64,18 @@ import net.fabricmc.fabric.api.registry.TillableBlockRegistry;
import net.fabricmc.fabric.api.registry.VillagerInteractionRegistries;
public final class ContentRegistryTest implements ModInitializer {
public static final String MOD_ID = "fabric-content-registries-v0-testmod";
public static final Logger LOGGER = LoggerFactory.getLogger(ContentRegistryTest.class);
public static final Identifier TEST_EVENT_ID = Identifier.of("fabric-content-registries-v0-testmod", "test_event");
public static final Item SMELTING_FUEL_INCLUDED_BY_ITEM = registerItem("smelting_fuel_included_by_item");
public static final Item SMELTING_FUEL_INCLUDED_BY_TAG = registerItem("smelting_fuel_included_by_tag");
public static final Item SMELTING_FUEL_EXCLUDED_BY_TAG = registerItem("smelting_fuel_excluded_by_tag");
public static final Item SMELTING_FUEL_EXCLUDED_BY_VANILLA_TAG = registerItem("smelting_fuel_excluded_by_vanilla_tag");
private static final TagKey<Item> SMELTING_FUELS_INCLUDED_BY_TAG = itemTag("smelting_fuels_included_by_tag");
private static final TagKey<Item> SMELTING_FUELS_EXCLUDED_BY_TAG = itemTag("smelting_fuels_excluded_by_tag");
public static final Identifier TEST_EVENT_ID = id("test_event");
public static final RegistryKey<Block> TEST_EVENT_BLOCK_KEY = RegistryKey.of(RegistryKeys.BLOCK, TEST_EVENT_ID);
public static final RegistryEntry.Reference<GameEvent> TEST_EVENT = Registry.registerReference(Registries.GAME_EVENT, TEST_EVENT_ID, new GameEvent(GameEvent.DEFAULT_RANGE));
@ -75,8 +86,7 @@ public final class ContentRegistryTest implements ModInitializer {
// - diamond block is now flammable
// - sand is now flammable
// - red wool is flattenable to yellow wool
// - obsidian is now fuel
// - all items with the tag 'minecraft:dirt' are now fuel
// - custom items prefixed with 'smelting fuels included by' are valid smelting fuels
// - dead bush is now considered as a dangerous block like sweet berry bushes (all entities except foxes should avoid it)
// - quartz pillars are strippable to hay blocks
// - green wool is tillable to lime wool
@ -94,6 +104,16 @@ public final class ContentRegistryTest implements ModInitializer {
FlammableBlockRegistry.getDefaultInstance().add(Blocks.DIAMOND_BLOCK, 4, 4);
FlammableBlockRegistry.getDefaultInstance().add(BlockTags.SAND, 4, 4);
FlattenableBlockRegistry.register(Blocks.RED_WOOL, Blocks.YELLOW_WOOL.getDefaultState());
FuelRegistryEvents.BUILD.register((builder, context) -> {
builder.add(SMELTING_FUEL_INCLUDED_BY_ITEM, context.baseSmeltTime() / 4);
builder.add(SMELTING_FUELS_INCLUDED_BY_TAG, context.baseSmeltTime() / 2);
});
FuelRegistryEvents.EXCLUSIONS.register((builder, context) -> {
builder.remove(SMELTING_FUELS_EXCLUDED_BY_TAG);
});
LandPathNodeTypesRegistry.register(Blocks.DEAD_BUSH, PathNodeType.DAMAGE_OTHER, PathNodeType.DANGER_OTHER);
StrippableBlockRegistry.register(Blocks.QUARTZ_PILLAR, Blocks.HAY_BLOCK);
@ -150,7 +170,7 @@ public final class ContentRegistryTest implements ModInitializer {
LOGGER.info("SculkSensorFrequencyRegistry test passed!");
}
RegistryKey<Item> dirtyPotionKey = RegistryKey.of(RegistryKeys.ITEM, Identifier.of("fabric-content-registries-v0-testmod", "dirty_potion"));
RegistryKey<Item> dirtyPotionKey = RegistryKey.of(RegistryKeys.ITEM, id("dirty_potion"));
var dirtyPotion = new DirtyPotionItem(new Item.Settings().maxCount(1).registryKey(dirtyPotionKey));
Registry.register(Registries.ITEM, dirtyPotionKey, dirtyPotion);
/* Mods should use BrewingRecipeRegistry.registerPotionType(Item), which is access widened by fabric-transitive-access-wideners-v1
@ -189,4 +209,17 @@ public final class ContentRegistryTest implements ModInitializer {
return Text.literal("Dirty ").append(Items.POTION.getName(stack));
}
}
private static Identifier id(String path) {
return Identifier.of(MOD_ID, path);
}
private static Item registerItem(String path) {
RegistryKey<Item> key = RegistryKey.of(RegistryKeys.ITEM, id(path));
return Registry.register(Registries.ITEM, key, new Item(new Item.Settings().registryKey(key)));
}
private static TagKey<Item> itemTag(String path) {
return TagKey.of(RegistryKeys.ITEM, id(path));
}
}

View file

@ -0,0 +1,9 @@
{
"item.fabric-content-registries-v0-testmod.smelting_fuel_excluded_by_item": "Smelting Fuel Excluded by Item",
"item.fabric-content-registries-v0-testmod.smelting_fuel_excluded_by_tag": "Smelting Fuel Excluded by Tag",
"item.fabric-content-registries-v0-testmod.smelting_fuel_excluded_by_vanilla_tag": "Smelting Fuel Excluded by Vanilla Tag",
"item.fabric-content-registries-v0-testmod.smelting_fuel_included_by_item": "Smelting Fuel Included by Item",
"item.fabric-content-registries-v0-testmod.smelting_fuel_included_by_tag": "Smelting Fuel Included by Tag",
"tag.item.fabric-content-registries-v0-testmod.smelting_fuels_excluded_by_tag": "Smelting Fuels Excluded by Tag",
"tag.item.fabric-content-registries-v0-testmod.smelting_fuels_included_by_tag": "Smelting Fuels Included by Tag"
}

View file

@ -0,0 +1,5 @@
{
"values": [
"fabric-content-registries-v0-testmod:smelting_fuel_excluded_by_tag"
]
}

View file

@ -0,0 +1,7 @@
{
"values": [
"fabric-content-registries-v0-testmod:smelting_fuel_included_by_tag",
"fabric-content-registries-v0-testmod:smelting_fuel_excluded_by_tag",
"fabric-content-registries-v0-testmod:smelting_fuel_excluded_by_vanilla_tag"
]
}

View file

@ -0,0 +1,5 @@
{
"values": [
"fabric-content-registries-v0-testmod:smelting_fuel_excluded_by_vanilla_tag"
]
}

View file

@ -42,6 +42,7 @@ import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.EnchantingContext;
import net.fabricmc.fabric.api.item.v1.EnchantmentEvents;
import net.fabricmc.fabric.api.registry.FabricBrewingRecipeRegistryBuilder;
import net.fabricmc.fabric.api.registry.FuelRegistryEvents;
import net.fabricmc.fabric.api.util.TriState;
public class CustomDamageTest implements ModInitializer {
@ -63,6 +64,7 @@ public class CustomDamageTest implements ModInitializer {
@Override
public void onInitialize() {
Registry.register(Registries.ITEM, WEIRD_PICK_KEY, WEIRD_PICK);
FuelRegistryEvents.BUILD.register((builder, context) -> builder.add(WEIRD_PICK, context.baseSmeltTime()));
FabricBrewingRecipeRegistryBuilder.BUILD.register(builder -> builder.registerPotionRecipe(Potions.WATER, WEIRD_PICK, Potions.AWKWARD));
EnchantmentEvents.ALLOW_ENCHANTING.register(((enchantment, target, enchantingContext) -> {
if (target.isOf(Items.DIAMOND_PICKAXE) && enchantment.matchesKey(Enchantments.SHARPNESS) && EnchantmentHelper.hasAnyEnchantmentsIn(target, EnchantmentTags.MINING_EXCLUSIVE_SET)) {

View file

@ -70,8 +70,7 @@ public class FurnaceGameTest implements FabricGameTest {
context.complete();
}
// TODO 1.21.2 - test broken as the weird pick does not have a fuel value, so doesnt burn.
// @GameTest(templateName = EMPTY_STRUCTURE)
@GameTest(templateName = EMPTY_STRUCTURE)
public void fabricRemainderTest(TestContext context) {
context.setBlockState(POS, Blocks.FURNACE);
FurnaceBlockEntity blockEntity = context.getBlockEntity(POS);