Modify Enchantment and Fabric Component Map Builder Extensions (#4085)

* modify effects event

* give impaling fire aspect

* add fabric component map builder

* change interface name to match event

* gametests for weird impaling enchantment

* fix checkstyle issues

* fabric map builder javadoc

* modify effects javadoc

* fix checkstyle issues

* prefer extension methods over add

* add enchantment source

* fix missing asterisk on fabricitemstack javadoc

* switch to enchantment builder

* fix effects list

* fix checkstyle

* add note on exclusive set to javadoc

* add fabric component builder extensions to default component testmod

* remove threadlocal usage from mixin

* remove modid prefix from accessors

* remove unused import

* fix recursive invoker

* add test to automatically check modified item name

(cherry picked from commit d5debaed0e)
This commit is contained in:
TheDeathlyCow 2024-09-24 05:46:27 +12:00 committed by modmuss50
parent 50ed0faf3a
commit ebc0bc7d18
18 changed files with 808 additions and 5 deletions

View file

@ -1,6 +1,6 @@
version = getSubprojectVersion(project) version = getSubprojectVersion(project)
moduleDependencies(project, ['fabric-api-base']) moduleDependencies(project, ['fabric-api-base', 'fabric-resource-loader-v0'])
testDependencies(project, [ testDependencies(project, [
':fabric-content-registries-v0', ':fabric-content-registries-v0',

View file

@ -18,6 +18,7 @@ package net.fabricmc.fabric.api.item.v1;
import net.minecraft.enchantment.Enchantment; import net.minecraft.enchantment.Enchantment;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.entry.RegistryEntry; import net.minecraft.registry.entry.RegistryEntry;
import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.Event;
@ -64,6 +65,27 @@ public final class EnchantmentEvents {
} }
); );
/**
* An event that allows an {@link Enchantment} to be modified without needing to fully override an enchantment.
*
* <p>This should only be used to modify the behavior of <em>external</em> enchantments, where 'external' means
* either vanilla or from another mod. For instance, a mod might add a bleed effect to Sharpness (and only Sharpness).
* For your own enchantments, you should simply define them in your mod's data pack. See the
* <a href="https://minecraft.wiki/w/Enchantment_definition">Enchantment Definition page</a> on the Minecraft Wiki
* for more information.
*
* <p>Note: If you wish to modify the exclusive set of the enchantment, consider extending the
* {@linkplain net.minecraft.registry.tag.EnchantmentTags relevant tag} through your mod's data pack instead.
*/
public static final Event<Modify> MODIFY = EventFactory.createArrayBacked(
Modify.class,
callbacks -> (key, builder, source) -> {
for (Modify callback : callbacks) {
callback.modify(key, builder, source);
}
}
);
@FunctionalInterface @FunctionalInterface
public interface AllowEnchanting { public interface AllowEnchanting {
/** /**
@ -82,4 +104,20 @@ public final class EnchantmentEvents {
EnchantingContext enchantingContext EnchantingContext enchantingContext
); );
} }
@FunctionalInterface
public interface Modify {
/**
* Modifies the effects of an {@link Enchantment}.
*
* @param key The ID of the enchantment
* @param builder The enchantment builder
* @param source The source of the enchantment
*/
void modify(
RegistryKey<Enchantment> key,
Enchantment.Builder builder,
EnchantmentSource source
);
}
} }

View file

@ -0,0 +1,55 @@
/*
* 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;
/**
* Determines where an enchantment has been loaded from.
*/
public enum EnchantmentSource {
/**
* An enchantment loaded from the vanilla data pack.
*/
VANILLA(true),
/**
* An enchantment loaded from mods' bundled resources.
*
* <p>This includes the additional builtin data packs registered by mods
* with Fabric Resource Loader.
*/
MOD(true),
/**
* An enchantment loaded from an external data pack.
*/
DATA_PACK(false);
private final boolean builtin;
EnchantmentSource(boolean builtin) {
this.builtin = builtin;
}
/**
* Returns whether this enchantment source is builtin and bundled in the vanilla or mod resources.
*
* <p>{@link #VANILLA} and {@link #MOD} are builtin.
*
* @return {@code true} if builtin, {@code false} otherwise
*/
public boolean isBuiltin() {
return builtin;
}
}

View file

@ -0,0 +1,74 @@
/*
* 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.List;
import java.util.Objects;
import java.util.function.Supplier;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import net.minecraft.component.ComponentType;
/**
* Fabric-provided extensions for {@link net.minecraft.component.ComponentMap.Builder}.
*
* <p>Note: This interface is automatically implemented on all component map builders via Mixin and interface injection.
*/
@ApiStatus.NonExtendable
public interface FabricComponentMapBuilder {
/**
* Gets the current value for the component type in the builder, or creates and adds a new value if it is not present.
*
* @param type The component type
* @param fallback The supplier for the default data value if the type is not in this map yet. The value given by this supplier
* may not be null.
* @param <T> The type of the component data
* @return Returns the current value in the map builder, or the default value provided by the fallback if not present
* @see #getOrEmpty(ComponentType)
*/
default <T> T getOrCreate(ComponentType<T> type, Supplier<@NotNull T> fallback) {
throw new AssertionError("Implemented in Mixin");
}
/**
* Gets the current value for the component type in the builder, or creates and adds a new value if it is not present.
*
* @param type The component type
* @param defaultValue The default data value if the type is not in this map yet
* @param <T> The type of the component data
* @return Returns the current value in the map builder, or the default value if not present
*/
default <T> T getOrDefault(ComponentType<T> type, @NotNull T defaultValue) {
Objects.requireNonNull(defaultValue, "Cannot insert null values to component map builder");
return getOrCreate(type, () -> defaultValue);
}
/**
* For list component types specifically, returns a mutable list of values currently held in the builder for the given
* component type. If the type is not registered to this builder yet, this will create and add a new empty list to the builder
* for the type, and return that.
*
* @param type The component type. The component must be a list-type.
* @param <T> The type of the component entry data
* @return Returns a mutable list of values for the type.
*/
default <T> List<T> getOrEmpty(ComponentType<List<T>> type) {
throw new AssertionError("Implemented in Mixin");
}
}

View file

@ -23,7 +23,7 @@ import net.minecraft.registry.entry.RegistryEntry;
import net.fabricmc.fabric.api.util.TriState; import net.fabricmc.fabric.api.util.TriState;
/* /**
* Fabric-provided extensions for {@link ItemStack}. * Fabric-provided extensions for {@link ItemStack}.
* This interface is automatically implemented on all item stacks via Mixin and interface injection. * This interface is automatically implemented on all item stacks via Mixin and interface injection.
*/ */

View file

@ -0,0 +1,80 @@
/*
* 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.List;
import net.minecraft.component.ComponentType;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.registry.RegistryKey;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourcePackSource;
import net.fabricmc.fabric.api.item.v1.EnchantmentEvents;
import net.fabricmc.fabric.api.item.v1.EnchantmentSource;
import net.fabricmc.fabric.impl.resource.loader.BuiltinModResourcePackSource;
import net.fabricmc.fabric.impl.resource.loader.FabricResource;
import net.fabricmc.fabric.impl.resource.loader.ModResourcePackCreator;
import net.fabricmc.fabric.mixin.item.EnchantmentBuilderAccessor;
public class EnchantmentUtil {
@SuppressWarnings("unchecked")
public static Enchantment modify(RegistryKey<Enchantment> key, Enchantment originalEnchantment, EnchantmentSource source) {
Enchantment.Builder builder = Enchantment.builder(originalEnchantment.definition());
EnchantmentBuilderAccessor accessor = (EnchantmentBuilderAccessor) builder;
builder.exclusiveSet(originalEnchantment.exclusiveSet());
accessor.getEffectMap().addAll(originalEnchantment.effects());
originalEnchantment.effects().stream()
.forEach(component -> {
if (component.value() instanceof List<?> valueList) {
// component type cast is checked by the value
accessor.invokeGetEffectsList((ComponentType<List<Object>>) component.type())
.addAll(valueList);
}
});
EnchantmentEvents.MODIFY.invoker().modify(key, builder, source);
return new Enchantment(
originalEnchantment.description(),
accessor.getDefinition(),
accessor.getExclusiveSet(),
accessor.getEffectMap().build()
);
}
public static EnchantmentSource determineSource(Resource resource) {
if (resource != null) {
ResourcePackSource packSource = ((FabricResource) resource).getFabricPackSource();
if (packSource == ResourcePackSource.BUILTIN) {
return EnchantmentSource.VANILLA;
} else if (packSource == ModResourcePackCreator.RESOURCE_PACK_SOURCE || packSource instanceof BuiltinModResourcePackSource) {
return EnchantmentSource.MOD;
}
}
// If not builtin or mod, assume external data pack.
// It might also be a virtual enchantment injected via mixin instead of being loaded
// from a resource, but we can't determine that here.
return EnchantmentSource.DATA_PACK;
}
private EnchantmentUtil() { }
}

View file

@ -0,0 +1,65 @@
/*
* 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 java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.function.Supplier;
import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.minecraft.component.ComponentMap;
import net.minecraft.component.ComponentType;
import net.fabricmc.fabric.api.item.v1.FabricComponentMapBuilder;
@Mixin(ComponentMap.Builder.class)
abstract class ComponentMapBuilderMixin implements FabricComponentMapBuilder {
@Shadow
@Final
private Reference2ObjectMap<ComponentType<?>, Object> components;
@Shadow
public abstract <T> ComponentMap.Builder add(ComponentType<T> type, @Nullable T value);
@Override
@SuppressWarnings("unchecked")
public <T> T getOrCreate(ComponentType<T> type, Supplier<@NotNull T> fallback) {
if (!this.components.containsKey(type)) {
T defaultValue = fallback.get();
Objects.requireNonNull(defaultValue, "Cannot insert null values to component map builder");
this.add(type, defaultValue);
}
return (T) this.components.get(type);
}
@Override
public <T> List<T> getOrEmpty(ComponentType<List<T>> type) {
// creating a new array list guarantees that the list in the map is mutable
List<T> existing = new ArrayList<>(this.getOrCreate(type, Collections::emptyList));
this.add(type, existing);
return existing;
}
}

View file

@ -0,0 +1,43 @@
/*
* 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 java.util.List;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.component.ComponentMap;
import net.minecraft.component.ComponentType;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.registry.entry.RegistryEntryList;
@Mixin(Enchantment.Builder.class)
public interface EnchantmentBuilderAccessor {
@Accessor("definition")
Enchantment.Definition getDefinition();
@Accessor("exclusiveSet")
RegistryEntryList<Enchantment> getExclusiveSet();
@Accessor("effectMap")
ComponentMap.Builder getEffectMap();
@Invoker("getEffectsList")
<E> List<E> invokeGetEffectsList(ComponentType<List<E>> type);
}

View file

@ -0,0 +1,66 @@
/*
* 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 com.google.gson.JsonElement;
import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import com.mojang.serialization.Decoder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.registry.MutableRegistry;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryLoader;
import net.minecraft.registry.RegistryOps;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.registry.entry.RegistryEntryInfo;
import net.minecraft.resource.Resource;
import net.fabricmc.fabric.impl.item.EnchantmentUtil;
@Mixin(RegistryLoader.class)
abstract class RegistryLoaderMixin {
@WrapOperation(
method = "parseAndAdd",
at = @At(
value = "INVOKE",
target = "Lnet/minecraft/registry/MutableRegistry;add(Lnet/minecraft/registry/RegistryKey;Ljava/lang/Object;Lnet/minecraft/registry/entry/RegistryEntryInfo;)Lnet/minecraft/registry/entry/RegistryEntry$Reference;"
)
)
@SuppressWarnings("unchecked")
private static <T> RegistryEntry.Reference<T> enchantmentKey(
MutableRegistry<T> instance,
RegistryKey<T> objectKey,
Object object,
RegistryEntryInfo registryEntryInfo,
Operation<RegistryEntry.Reference<T>> original,
MutableRegistry<T> registry,
Decoder<T> decoder,
RegistryOps<JsonElement> ops,
RegistryKey<T> registryKey,
Resource resource,
RegistryEntryInfo entryInfo
) {
if (object instanceof Enchantment enchantment) {
object = EnchantmentUtil.modify((RegistryKey<Enchantment>) objectKey, enchantment, EnchantmentUtil.determineSource(resource));
}
return original.call(instance, registryKey, object, registryEntryInfo);
}
}

View file

@ -6,6 +6,8 @@
"AbstractFurnaceBlockEntityMixin", "AbstractFurnaceBlockEntityMixin",
"AnvilScreenHandlerMixin", "AnvilScreenHandlerMixin",
"BrewingStandBlockEntityMixin", "BrewingStandBlockEntityMixin",
"ComponentMapBuilderMixin",
"EnchantmentBuilderAccessor",
"EnchantCommandMixin", "EnchantCommandMixin",
"EnchantmentHelperMixin", "EnchantmentHelperMixin",
"EnchantRandomlyLootFunctionMixin", "EnchantRandomlyLootFunctionMixin",
@ -15,7 +17,8 @@
"ItemStackMixin", "ItemStackMixin",
"LivingEntityMixin", "LivingEntityMixin",
"RecipeMixin", "RecipeMixin",
"RegistriesMixin" "RegistriesMixin",
"RegistryLoaderMixin"
], ],
"injectors": { "injectors": {
"defaultRequire": 1 "defaultRequire": 1

View file

@ -32,7 +32,8 @@
"loom:injected_interfaces": { "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_1792\u0024class_1793": ["net/fabricmc/fabric/api/item/v1/FabricItem\u0024Settings"], "net/minecraft/class_1792\u0024class_1793": ["net/fabricmc/fabric/api/item/v1/FabricItem\u0024Settings"],
"net/minecraft/class_1799": ["net/fabricmc/fabric/api/item/v1/FabricItemStack"] "net/minecraft/class_1799": ["net/fabricmc/fabric/api/item/v1/FabricItemStack"],
"net/minecraft/class_9323\u0024class_9324": ["net/fabricmc/fabric/api/item/v1/FabricComponentMapBuilder"]
} }
} }
} }

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;
import net.minecraft.component.EnchantmentEffectComponentTypes;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.EnchantmentLevelBasedValue;
import net.minecraft.enchantment.effect.EnchantmentEffectTarget;
import net.minecraft.enchantment.effect.entity.IgniteEnchantmentEffect;
import net.minecraft.enchantment.effect.value.AddEnchantmentEffect;
import net.minecraft.entity.EntityType;
import net.minecraft.loot.condition.DamageSourcePropertiesLootCondition;
import net.minecraft.loot.condition.EntityPropertiesLootCondition;
import net.minecraft.loot.context.LootContext;
import net.minecraft.predicate.entity.DamageSourcePredicate;
import net.minecraft.predicate.entity.EntityPredicate;
import net.minecraft.predicate.entity.EntityTypePredicate;
import net.minecraft.registry.RegistryKey;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.util.Identifier;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.EnchantmentEvents;
public class CustomEnchantmentEffectsTest implements ModInitializer {
// weird impaling is a copy of impaling used for testing (just in case minecraft changes impaling for some reason)
public static final RegistryKey<Enchantment> WEIRD_IMPALING = RegistryKey.of(
RegistryKeys.ENCHANTMENT,
Identifier.of("fabric-item-api-v1-testmod", "weird_impaling")
);
@Override
public void onInitialize() {
EnchantmentEvents.MODIFY.register(
(key, builder, source) -> {
if (source.isBuiltin() && key == WEIRD_IMPALING) {
// make impaling set things on fire
builder.addEffect(
EnchantmentEffectComponentTypes.POST_ATTACK,
EnchantmentEffectTarget.ATTACKER,
EnchantmentEffectTarget.VICTIM,
new IgniteEnchantmentEffect(EnchantmentLevelBasedValue.linear(4.0f)),
DamageSourcePropertiesLootCondition.builder(
DamageSourcePredicate.Builder.create().isDirect(true)
)
);
// add bonus impaling damage to zombie
builder.addEffect(
EnchantmentEffectComponentTypes.DAMAGE,
new AddEnchantmentEffect(EnchantmentLevelBasedValue.linear(2.5f)),
EntityPropertiesLootCondition.builder(
LootContext.EntityTarget.THIS,
EntityPredicate.Builder.create()
.type(EntityTypePredicate.create(EntityType.ZOMBIE))
)
);
}
}
);
}
}

View file

@ -51,6 +51,14 @@ public class DefaultItemComponentTest implements ModInitializer {
// Remove the food component from beef // Remove the food component from beef
builder.add(DataComponentTypes.FOOD, null); builder.add(DataComponentTypes.FOOD, null);
}); });
// add a word to the start of diamond pickaxe name
context.modify(Items.DIAMOND_PICKAXE, builder -> {
Text baseName = builder.getOrCreate(
DataComponentTypes.ITEM_NAME,
Items.DIAMOND_PICKAXE::getName
);
builder.add(DataComponentTypes.ITEM_NAME, prependModifiedLiteral(baseName));
});
}); });
// Make all fireworks glint // Make all fireworks glint
@ -60,4 +68,9 @@ public class DefaultItemComponentTest implements ModInitializer {
}); });
}); });
} }
public static Text prependModifiedLiteral(Text name) {
return Text.literal("Modified ")
.append(name);
}
} }

View file

@ -0,0 +1,91 @@
/*
* 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.List;
import java.util.Optional;
import net.minecraft.component.EnchantmentEffectComponentTypes;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.enchantment.effect.EnchantmentEffectEntry;
import net.minecraft.enchantment.effect.EnchantmentValueEffect;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityType;
import net.minecraft.entity.mob.CreeperEntity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.registry.DynamicRegistryManager;
import net.minecraft.registry.Registry;
import net.minecraft.registry.RegistryKeys;
import net.minecraft.registry.entry.RegistryEntry;
import net.minecraft.test.GameTest;
import net.minecraft.test.GameTestException;
import net.minecraft.test.TestContext;
import net.minecraft.util.Hand;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.GameMode;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.fabric.test.item.CustomEnchantmentEffectsTest;
public class CustomEnchantmentEffectsGameTest implements FabricGameTest {
@GameTest(templateName = "fabric-item-api-v1-testmod:bedrock_platform")
public void weirdImpalingSetsFireToTargets(TestContext context) {
BlockPos pos = new BlockPos(3, 3, 3);
CreeperEntity creeper = context.spawnEntity(EntityType.CREEPER, pos);
PlayerEntity player = context.createMockPlayer(GameMode.CREATIVE);
ItemStack trident = Items.TRIDENT.getDefaultStack();
Optional<RegistryEntry.Reference<Enchantment>> impaling = getEnchantmentRegistry(context)
.getEntry(CustomEnchantmentEffectsTest.WEIRD_IMPALING);
if (impaling.isEmpty()) {
throw new GameTestException("Weird Impaling enchantment is not present");
}
trident.addEnchantment(impaling.get(), 1);
player.setStackInHand(Hand.MAIN_HAND, trident);
context.expectEntityWithData(pos, EntityType.CREEPER, Entity::isOnFire, false);
player.attack(creeper);
context.expectEntityWithDataEnd(pos, EntityType.CREEPER, Entity::isOnFire, true);
}
@GameTest(templateName = EMPTY_STRUCTURE)
public void weirdImpalingHasTwoDamageEffects(TestContext context) {
Enchantment impaling = getEnchantmentRegistry(context).get(CustomEnchantmentEffectsTest.WEIRD_IMPALING);
if (impaling == null) {
throw new GameTestException("Weird Impaling enchantment is not present");
}
List<EnchantmentEffectEntry<EnchantmentValueEffect>> damageEffects = impaling
.getEffect(EnchantmentEffectComponentTypes.DAMAGE);
context.assertTrue(
damageEffects.size() == 2,
String.format("Weird Impaling has %d damage effect(s), not the expected 2", damageEffects.size())
);
context.complete();
}
private static Registry<Enchantment> getEnchantmentRegistry(TestContext context) {
DynamicRegistryManager registryManager = context.getWorld().getRegistryManager();
return registryManager.get(RegistryKeys.ENCHANTMENT);
}
}

View file

@ -20,6 +20,7 @@ import java.util.function.Consumer;
import net.minecraft.component.DataComponentTypes; import net.minecraft.component.DataComponentTypes;
import net.minecraft.component.type.FireworksComponent; import net.minecraft.component.type.FireworksComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.item.Items; import net.minecraft.item.Items;
import net.minecraft.test.GameTest; import net.minecraft.test.GameTest;
@ -28,6 +29,7 @@ import net.minecraft.test.TestContext;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import net.fabricmc.fabric.api.gametest.v1.FabricGameTest; import net.fabricmc.fabric.api.gametest.v1.FabricGameTest;
import net.fabricmc.fabric.test.item.DefaultItemComponentTest;
public class DefaultItemComponentGameTest implements FabricGameTest { public class DefaultItemComponentGameTest implements FabricGameTest {
@GameTest(templateName = EMPTY_STRUCTURE) @GameTest(templateName = EMPTY_STRUCTURE)
@ -73,4 +75,20 @@ public class DefaultItemComponentGameTest implements FabricGameTest {
context.complete(); context.complete();
} }
@GameTest(templateName = EMPTY_STRUCTURE)
public void diamondPickaxeIsRenamed(TestContext context) {
Item testItem = Items.DIAMOND_PICKAXE;
ItemStack stack = testItem.getDefaultStack();
Text itemName = stack.getOrDefault(DataComponentTypes.ITEM_NAME, Text.literal(""));
Text expectedName = DefaultItemComponentTest.prependModifiedLiteral(testItem.getName());
String errorMessage = "Expected '%s' to be contained in '%s', but it was not!";
// if they contain each other, then they are equal
context.assertTrue(itemName.contains(expectedName), errorMessage.formatted(expectedName, itemName));
context.assertTrue(expectedName.contains(itemName), errorMessage.formatted(itemName, expectedName));
context.complete();
}
} }

View file

@ -0,0 +1,42 @@
{
"description": {
"text": "Weird Impaling"
},
"exclusive_set": "#minecraft:exclusive_set/damage",
"supported_items": "#minecraft:enchantable/trident",
"weight": 2,
"max_level": 5,
"min_cost": {
"base": 1,
"per_level_above_first": 8
},
"max_cost": {
"base": 21,
"per_level_above_first": 8
},
"anvil_cost": 4,
"slots": [
"mainhand"
],
"effects": {
"minecraft:damage": [
{
"effect": {
"type": "minecraft:add",
"value": {
"type": "minecraft:linear",
"base": 2.5,
"per_level_above_first": 2.5
}
},
"requirements": {
"condition": "minecraft:entity_properties",
"entity": "this",
"predicate": {
"type": "#minecraft:sensitive_to_impaling"
}
}
}
]
}
}

View file

@ -0,0 +1,136 @@
{
DataVersion: 3955,
size: [5, 5, 5],
data: [
{pos: [0, 0, 0], state: "minecraft:bedrock"},
{pos: [0, 0, 1], state: "minecraft:bedrock"},
{pos: [0, 0, 2], state: "minecraft:bedrock"},
{pos: [0, 0, 3], state: "minecraft:bedrock"},
{pos: [0, 0, 4], state: "minecraft:bedrock"},
{pos: [1, 0, 0], state: "minecraft:bedrock"},
{pos: [1, 0, 1], state: "minecraft:bedrock"},
{pos: [1, 0, 2], state: "minecraft:bedrock"},
{pos: [1, 0, 3], state: "minecraft:bedrock"},
{pos: [1, 0, 4], state: "minecraft:bedrock"},
{pos: [2, 0, 0], state: "minecraft:bedrock"},
{pos: [2, 0, 1], state: "minecraft:bedrock"},
{pos: [2, 0, 2], state: "minecraft:bedrock"},
{pos: [2, 0, 3], state: "minecraft:bedrock"},
{pos: [2, 0, 4], state: "minecraft:bedrock"},
{pos: [3, 0, 0], state: "minecraft:bedrock"},
{pos: [3, 0, 1], state: "minecraft:bedrock"},
{pos: [3, 0, 2], state: "minecraft:bedrock"},
{pos: [3, 0, 3], state: "minecraft:bedrock"},
{pos: [3, 0, 4], state: "minecraft:bedrock"},
{pos: [4, 0, 0], state: "minecraft:bedrock"},
{pos: [4, 0, 1], state: "minecraft:bedrock"},
{pos: [4, 0, 2], state: "minecraft:bedrock"},
{pos: [4, 0, 3], state: "minecraft:bedrock"},
{pos: [4, 0, 4], state: "minecraft:bedrock"},
{pos: [0, 1, 0], state: "minecraft:air"},
{pos: [0, 1, 1], state: "minecraft:air"},
{pos: [0, 1, 2], state: "minecraft:air"},
{pos: [0, 1, 3], state: "minecraft:air"},
{pos: [0, 1, 4], state: "minecraft:air"},
{pos: [1, 1, 0], state: "minecraft:air"},
{pos: [1, 1, 1], state: "minecraft:air"},
{pos: [1, 1, 2], state: "minecraft:air"},
{pos: [1, 1, 3], state: "minecraft:air"},
{pos: [1, 1, 4], state: "minecraft:air"},
{pos: [2, 1, 0], state: "minecraft:air"},
{pos: [2, 1, 1], state: "minecraft:air"},
{pos: [2, 1, 2], state: "minecraft:air"},
{pos: [2, 1, 3], state: "minecraft:air"},
{pos: [2, 1, 4], state: "minecraft:air"},
{pos: [3, 1, 0], state: "minecraft:air"},
{pos: [3, 1, 1], state: "minecraft:air"},
{pos: [3, 1, 2], state: "minecraft:air"},
{pos: [3, 1, 3], state: "minecraft:air"},
{pos: [3, 1, 4], state: "minecraft:air"},
{pos: [4, 1, 0], state: "minecraft:air"},
{pos: [4, 1, 1], state: "minecraft:air"},
{pos: [4, 1, 2], state: "minecraft:air"},
{pos: [4, 1, 3], state: "minecraft:air"},
{pos: [4, 1, 4], state: "minecraft:air"},
{pos: [0, 2, 0], state: "minecraft:air"},
{pos: [0, 2, 1], state: "minecraft:air"},
{pos: [0, 2, 2], state: "minecraft:air"},
{pos: [0, 2, 3], state: "minecraft:air"},
{pos: [0, 2, 4], state: "minecraft:air"},
{pos: [1, 2, 0], state: "minecraft:air"},
{pos: [1, 2, 1], state: "minecraft:air"},
{pos: [1, 2, 2], state: "minecraft:air"},
{pos: [1, 2, 3], state: "minecraft:air"},
{pos: [1, 2, 4], state: "minecraft:air"},
{pos: [2, 2, 0], state: "minecraft:air"},
{pos: [2, 2, 1], state: "minecraft:air"},
{pos: [2, 2, 2], state: "minecraft:air"},
{pos: [2, 2, 3], state: "minecraft:air"},
{pos: [2, 2, 4], state: "minecraft:air"},
{pos: [3, 2, 0], state: "minecraft:air"},
{pos: [3, 2, 1], state: "minecraft:air"},
{pos: [3, 2, 2], state: "minecraft:air"},
{pos: [3, 2, 3], state: "minecraft:air"},
{pos: [3, 2, 4], state: "minecraft:air"},
{pos: [4, 2, 0], state: "minecraft:air"},
{pos: [4, 2, 1], state: "minecraft:air"},
{pos: [4, 2, 2], state: "minecraft:air"},
{pos: [4, 2, 3], state: "minecraft:air"},
{pos: [4, 2, 4], state: "minecraft:air"},
{pos: [0, 3, 0], state: "minecraft:air"},
{pos: [0, 3, 1], state: "minecraft:air"},
{pos: [0, 3, 2], state: "minecraft:air"},
{pos: [0, 3, 3], state: "minecraft:air"},
{pos: [0, 3, 4], state: "minecraft:air"},
{pos: [1, 3, 0], state: "minecraft:air"},
{pos: [1, 3, 1], state: "minecraft:air"},
{pos: [1, 3, 2], state: "minecraft:air"},
{pos: [1, 3, 3], state: "minecraft:air"},
{pos: [1, 3, 4], state: "minecraft:air"},
{pos: [2, 3, 0], state: "minecraft:air"},
{pos: [2, 3, 1], state: "minecraft:air"},
{pos: [2, 3, 2], state: "minecraft:air"},
{pos: [2, 3, 3], state: "minecraft:air"},
{pos: [2, 3, 4], state: "minecraft:air"},
{pos: [3, 3, 0], state: "minecraft:air"},
{pos: [3, 3, 1], state: "minecraft:air"},
{pos: [3, 3, 2], state: "minecraft:air"},
{pos: [3, 3, 3], state: "minecraft:air"},
{pos: [3, 3, 4], state: "minecraft:air"},
{pos: [4, 3, 0], state: "minecraft:air"},
{pos: [4, 3, 1], state: "minecraft:air"},
{pos: [4, 3, 2], state: "minecraft:air"},
{pos: [4, 3, 3], state: "minecraft:air"},
{pos: [4, 3, 4], state: "minecraft:air"},
{pos: [0, 4, 0], state: "minecraft:air"},
{pos: [0, 4, 1], state: "minecraft:air"},
{pos: [0, 4, 2], state: "minecraft:air"},
{pos: [0, 4, 3], state: "minecraft:air"},
{pos: [0, 4, 4], state: "minecraft:air"},
{pos: [1, 4, 0], state: "minecraft:air"},
{pos: [1, 4, 1], state: "minecraft:air"},
{pos: [1, 4, 2], state: "minecraft:air"},
{pos: [1, 4, 3], state: "minecraft:air"},
{pos: [1, 4, 4], state: "minecraft:air"},
{pos: [2, 4, 0], state: "minecraft:air"},
{pos: [2, 4, 1], state: "minecraft:air"},
{pos: [2, 4, 2], state: "minecraft:air"},
{pos: [2, 4, 3], state: "minecraft:air"},
{pos: [2, 4, 4], state: "minecraft:air"},
{pos: [3, 4, 0], state: "minecraft:air"},
{pos: [3, 4, 1], state: "minecraft:air"},
{pos: [3, 4, 2], state: "minecraft:air"},
{pos: [3, 4, 3], state: "minecraft:air"},
{pos: [3, 4, 4], state: "minecraft:air"},
{pos: [4, 4, 0], state: "minecraft:air"},
{pos: [4, 4, 1], state: "minecraft:air"},
{pos: [4, 4, 2], state: "minecraft:air"},
{pos: [4, 4, 3], state: "minecraft:air"},
{pos: [4, 4, 4], state: "minecraft:air"}
],
entities: [],
palette: [
"minecraft:bedrock",
"minecraft:air"
]
}

View file

@ -13,13 +13,15 @@
"net.fabricmc.fabric.test.item.CustomDamageTest", "net.fabricmc.fabric.test.item.CustomDamageTest",
"net.fabricmc.fabric.test.item.DefaultItemComponentTest", "net.fabricmc.fabric.test.item.DefaultItemComponentTest",
"net.fabricmc.fabric.test.item.ItemUpdateAnimationTest", "net.fabricmc.fabric.test.item.ItemUpdateAnimationTest",
"net.fabricmc.fabric.test.item.ArmorKnockbackResistanceTest" "net.fabricmc.fabric.test.item.ArmorKnockbackResistanceTest",
"net.fabricmc.fabric.test.item.CustomEnchantmentEffectsTest"
], ],
"client": [ "client": [
"net.fabricmc.fabric.test.item.client.TooltipTests" "net.fabricmc.fabric.test.item.client.TooltipTests"
], ],
"fabric-gametest" : [ "fabric-gametest" : [
"net.fabricmc.fabric.test.item.gametest.BrewingStandGameTest", "net.fabricmc.fabric.test.item.gametest.BrewingStandGameTest",
"net.fabricmc.fabric.test.item.gametest.CustomEnchantmentEffectsGameTest",
"net.fabricmc.fabric.test.item.gametest.DefaultItemComponentGameTest", "net.fabricmc.fabric.test.item.gametest.DefaultItemComponentGameTest",
"net.fabricmc.fabric.test.item.gametest.FurnaceGameTest", "net.fabricmc.fabric.test.item.gametest.FurnaceGameTest",
"net.fabricmc.fabric.test.item.gametest.RecipeGameTest" "net.fabricmc.fabric.test.item.gametest.RecipeGameTest"