Add API to modify default item components (#3728)

* Add API to modify default item components

* Add test for removal

* Some review feedback

* API design changes

* Review feedback

* Add overload that takes a Collection<Item>
This commit is contained in:
modmuss 2024-05-07 19:03:43 +01:00 committed by GitHub
parent c0e5481f61
commit 5bcea88aba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 344 additions and 1 deletions

View file

@ -0,0 +1,84 @@
/*
* 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 java.util.Collection;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Predicate;
import net.minecraft.component.ComponentMap;
import net.minecraft.item.Item;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Events to modify the default {@link ComponentMap} of items.
*/
public final class DefaultItemComponentEvents {
/**
* Event used to add or remove data components to known items.
*/
public static final Event<ModifyCallback> MODIFY = EventFactory.createArrayBacked(ModifyCallback.class, listeners -> context -> {
for (ModifyCallback listener : listeners) {
listener.modify(context);
}
});
private DefaultItemComponentEvents() {
}
public interface ModifyContext {
/**
* Modify the default data components of the specified item.
*
* @param itemPredicate A predicate to match items to modify
* @param builderConsumer A consumer that provides a {@link ComponentMap.Builder} to modify the item's components.
*/
void modify(Predicate<Item> itemPredicate, BiConsumer<ComponentMap.Builder, Item> builderConsumer);
/**
* Modify the default data components of the specified item.
*
* @param item The item to modify
* @param builderConsumer A consumer that provides a {@link ComponentMap.Builder} to modify the item's components.
*/
default void modify(Item item, Consumer<ComponentMap.Builder> builderConsumer) {
modify(Predicate.isEqual(item), (builder, _item) -> builderConsumer.accept(builder));
}
/**
* Modify the default data components of the specified items.
* @param items The items to modify
* @param builderConsumer A consumer that provides a {@link ComponentMap.Builder} to modify the item's components.
*/
default void modify(Collection<Item> items, BiConsumer<ComponentMap.Builder, Item> builderConsumer) {
modify(items::contains, builderConsumer);
}
}
@FunctionalInterface
public interface ModifyCallback {
/**
* Modify the default data components of items using the provided {@link ModifyContext} instance.
*
* @param context The context to modify items
*/
void modify(ModifyContext context);
}
}

View file

@ -0,0 +1,51 @@
/*
* 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 java.util.function.BiConsumer;
import java.util.function.Predicate;
import net.minecraft.component.ComponentMap;
import net.minecraft.item.Item;
import net.minecraft.registry.Registries;
import net.fabricmc.fabric.api.item.v1.DefaultItemComponentEvents;
import net.fabricmc.fabric.mixin.item.ItemAccessor;
public class DefaultItemComponentImpl {
public static void modifyItemComponents() {
DefaultItemComponentEvents.MODIFY.invoker().modify(ModifyContextImpl.INSTANCE);
}
static class ModifyContextImpl implements DefaultItemComponentEvents.ModifyContext {
private static final ModifyContextImpl INSTANCE = new ModifyContextImpl();
private ModifyContextImpl() {
}
@Override
public void modify(Predicate<Item> itemPredicate, BiConsumer<ComponentMap.Builder, Item> builderConsumer) {
for (Item item : Registries.ITEM) {
if (itemPredicate.test(item)) {
ComponentMap.Builder builder = ComponentMap.builder().addAll(item.getComponents());
builderConsumer.accept(builder, item);
((ItemAccessor) item).setComponents(builder.build());
}
}
}
}
}

View file

@ -0,0 +1,31 @@
/*
* 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.Mutable;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.component.ComponentMap;
import net.minecraft.item.Item;
@Mixin(Item.class)
public interface ItemAccessor {
@Accessor
@Mutable
void setComponents(ComponentMap components);
}

View file

@ -0,0 +1,34 @@
/*
* 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.callback.CallbackInfo;
import net.minecraft.registry.Registries;
import net.fabricmc.fabric.impl.item.DefaultItemComponentImpl;
@Mixin(Registries.class)
public abstract class RegistriesMixin {
@Inject(method = "freezeRegistries", at = @At("HEAD"))
private static void modifyDefaultItemComponents(CallbackInfo ci) {
DefaultItemComponentImpl.modifyItemComponents();
}
}

View file

@ -9,11 +9,13 @@
"EnchantCommandMixin",
"EnchantmentHelperMixin",
"EnchantRandomlyLootFunctionMixin",
"ItemAccessor",
"ItemMixin",
"ItemSettingsMixin",
"ItemStackMixin",
"LivingEntityMixin",
"RecipeMixin"
"RecipeMixin",
"RegistriesMixin"
],
"injectors": {
"defaultRequire": 1

View file

@ -0,0 +1,63 @@
/*
* 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;
import java.util.List;
import it.unimi.dsi.fastutil.ints.IntList;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.FireworkExplosionComponent;
import net.minecraft.component.type.FireworksComponent;
import net.minecraft.item.Items;
import net.minecraft.text.Text;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.item.v1.DefaultItemComponentEvents;
public class DefaultItemComponentTest implements ModInitializer {
@Override
public void onInitialize() {
Identifier latePhase = new Identifier("fabric-item-api-v1-testmod", "late");
DefaultItemComponentEvents.MODIFY.addPhaseOrdering(Event.DEFAULT_PHASE, latePhase);
DefaultItemComponentEvents.MODIFY.register(context -> {
context.modify(Items.GOLD_INGOT, builder -> {
builder.add(DataComponentTypes.ITEM_NAME, Text.literal("Fool's Gold").formatted(Formatting.GOLD));
});
context.modify(Items.GOLD_NUGGET, builder -> {
builder.add(DataComponentTypes.FIREWORKS, new FireworksComponent(1, List.of(
new FireworkExplosionComponent(FireworkExplosionComponent.Type.STAR, IntList.of(0x32a852), IntList.of(0x32a852), true, true)
)));
});
context.modify(Items.BEEF, builder -> {
// Remove the food component from beef
builder.add(DataComponentTypes.FOOD, null);
});
});
// Make all fireworks glint
DefaultItemComponentEvents.MODIFY.register(latePhase, context -> {
context.modify(item -> item.getComponents().contains(DataComponentTypes.FIREWORKS), (builder, item) -> {
builder.add(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE, true);
});
});
}
}

View file

@ -0,0 +1,76 @@
/*
* 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.function.Consumer;
import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.FireworksComponent;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.test.GameTest;
import net.minecraft.test.GameTestException;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
public class DefaultItemComponentGameTest implements FabricGameTest {
@GameTest(templateName = EMPTY_STRUCTURE)
public void modify(TestContext context) {
Consumer<Text> checkText = text -> {
if (text == null) {
throw new GameTestException("Item name component not found on gold ingot");
}
if (!"Fool's Gold".equals(text.getString())) {
throw new GameTestException("Item name component on gold ingot is not set");
}
};
Text text = Items.GOLD_INGOT.getComponents().get(DataComponentTypes.ITEM_NAME);
checkText.accept(text);
text = new ItemStack(Items.GOLD_INGOT).getComponents().get(DataComponentTypes.ITEM_NAME);
checkText.accept(text);
boolean isBeefFood = Items.BEEF.getComponents().contains(DataComponentTypes.FOOD);
if (isBeefFood) {
throw new GameTestException("Food component not removed from beef");
}
context.complete();
}
@GameTest(templateName = EMPTY_STRUCTURE)
public void afterModify(TestContext context) {
FireworksComponent fireworksComponent = Items.GOLD_NUGGET.getComponents().get(DataComponentTypes.FIREWORKS);
if (fireworksComponent == null) {
throw new GameTestException("Fireworks component not found on gold nugget");
}
Boolean enchantGlint = Items.GOLD_NUGGET.getComponents().get(DataComponentTypes.ENCHANTMENT_GLINT_OVERRIDE);
if (enchantGlint != Boolean.TRUE) {
throw new GameTestException("Enchantment glint override not set on gold nugget");
}
context.complete();
}
}

View file

@ -11,6 +11,7 @@
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.item.CustomDamageTest",
"net.fabricmc.fabric.test.item.DefaultItemComponentTest",
"net.fabricmc.fabric.test.item.ItemUpdateAnimationTest",
"net.fabricmc.fabric.test.item.ArmorKnockbackResistanceTest"
],
@ -19,6 +20,7 @@
],
"fabric-gametest" : [
"net.fabricmc.fabric.test.item.gametest.BrewingStandGameTest",
"net.fabricmc.fabric.test.item.gametest.DefaultItemComponentGameTest",
"net.fabricmc.fabric.test.item.gametest.FurnaceGameTest",
"net.fabricmc.fabric.test.item.gametest.RecipeGameTest"
]