Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fuel registry events #4038

Merged
merged 8 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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 {
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"BrewingRecipeRegistryBuilderMixin",
"PathContextMixin",
"FarmerWorkTaskAccessor",
"FuelRegistryMixin",
"GiveGiftsToHeroTaskAccessor",
"GiveGiftsToHeroTaskMixin",
"HoeItemAccessor",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -31,6 +36,7 @@
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;
Expand Down Expand Up @@ -63,6 +69,87 @@ public void testFlattenableBlockRegistry(TestContext context) {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
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;
Expand All @@ -54,6 +55,7 @@
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;
Expand All @@ -62,9 +64,18 @@
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));

Expand All @@ -75,8 +86,7 @@ public void onInitialize() {
// - 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
Expand All @@ -94,6 +104,16 @@ public void onInitialize() {
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);

Expand Down Expand Up @@ -150,7 +170,7 @@ public void onInitialize() {
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
Expand Down Expand Up @@ -189,4 +209,17 @@ public Text getName(ItemStack stack) {
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));
}
}
Loading
Loading