From 0bc0638b50a7ebf76ed8d95e1b74b1a53746787c Mon Sep 17 00:00:00 2001 From: XFactHD Date: Fri, 18 Oct 2024 20:46:51 +0200 Subject: [PATCH] Implement support for preventing effects from being removed by milk or totems --- .../item/component/Consumables.java.patch | 17 ++ .../item/component/DeathProtection.java.patch | 17 ++ .../tags/mob_effect/not_milk_curable.json | 3 + .../tags/mob_effect/not_totem_curable.json | 3 + .../neoforge/common/NeoForgeMod.java | 10 ++ .../net/neoforged/neoforge/common/Tags.java | 11 ++ .../NeoForgeMobEffectsTagsProvider.java | 29 ++++ .../holdersets/LazyNamedHolderSet.java | 160 ++++++++++++++++++ .../tags/mob_effect/not_milk_curable.json | 5 + .../neoforge/debug/effect/MobEffectTests.java | 82 ++++----- 10 files changed, 299 insertions(+), 38 deletions(-) create mode 100644 patches/net/minecraft/world/item/component/Consumables.java.patch create mode 100644 patches/net/minecraft/world/item/component/DeathProtection.java.patch create mode 100644 src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json create mode 100644 src/generated/resources/data/neoforge/tags/mob_effect/not_totem_curable.json create mode 100644 src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeMobEffectsTagsProvider.java create mode 100644 src/main/java/net/neoforged/neoforge/registries/holdersets/LazyNamedHolderSet.java create mode 100644 tests/src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json diff --git a/patches/net/minecraft/world/item/component/Consumables.java.patch b/patches/net/minecraft/world/item/component/Consumables.java.patch new file mode 100644 index 0000000000..f9b750b212 --- /dev/null +++ b/patches/net/minecraft/world/item/component/Consumables.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/item/component/Consumables.java ++++ b/net/minecraft/world/item/component/Consumables.java +@@ -62,7 +_,13 @@ + public static final Consumable SPIDER_EYE = defaultFood() + .onConsume(new ApplyStatusEffectsConsumeEffect(new MobEffectInstance(MobEffects.POISON, 100, 0))) + .build(); +- public static final Consumable MILK_BUCKET = defaultDrink().onConsume(ClearAllStatusEffectsConsumeEffect.INSTANCE).build(); ++ public static final Consumable MILK_BUCKET = defaultDrink().onConsume(new RemoveStatusEffectsConsumeEffect(new net.neoforged.neoforge.registries.holdersets.NotHolderSet<>( ++ net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT, ++ new net.neoforged.neoforge.registries.holdersets.LazyNamedHolderSet<>( ++ net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT, ++ net.neoforged.neoforge.common.Tags.MobEffects.NOT_MILK_CURABLE ++ ) ++ ))).build(); + public static final Consumable CHORUS_FRUIT = defaultFood().onConsume(new TeleportRandomlyConsumeEffect()).build(); + + public static Consumable.Builder defaultFood() { diff --git a/patches/net/minecraft/world/item/component/DeathProtection.java.patch b/patches/net/minecraft/world/item/component/DeathProtection.java.patch new file mode 100644 index 0000000000..e4c970203b --- /dev/null +++ b/patches/net/minecraft/world/item/component/DeathProtection.java.patch @@ -0,0 +1,17 @@ +--- a/net/minecraft/world/item/component/DeathProtection.java ++++ b/net/minecraft/world/item/component/DeathProtection.java +@@ -25,7 +_,13 @@ + ); + public static final DeathProtection TOTEM_OF_UNDYING = new DeathProtection( + List.of( +- new ClearAllStatusEffectsConsumeEffect(), ++ new net.minecraft.world.item.consume_effects.RemoveStatusEffectsConsumeEffect(new net.neoforged.neoforge.registries.holdersets.NotHolderSet<>( ++ net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT, ++ new net.neoforged.neoforge.registries.holdersets.LazyNamedHolderSet<>( ++ net.minecraft.core.registries.BuiltInRegistries.MOB_EFFECT, ++ net.neoforged.neoforge.common.Tags.MobEffects.NOT_TOTEM_CURABLE ++ ) ++ )), + new ApplyStatusEffectsConsumeEffect( + List.of( + new MobEffectInstance(MobEffects.REGENERATION, 900, 1), diff --git a/src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json b/src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json new file mode 100644 index 0000000000..f72d209df7 --- /dev/null +++ b/src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json @@ -0,0 +1,3 @@ +{ + "values": [] +} \ No newline at end of file diff --git a/src/generated/resources/data/neoforge/tags/mob_effect/not_totem_curable.json b/src/generated/resources/data/neoforge/tags/mob_effect/not_totem_curable.json new file mode 100644 index 0000000000..f72d209df7 --- /dev/null +++ b/src/generated/resources/data/neoforge/tags/mob_effect/not_totem_curable.json @@ -0,0 +1,3 @@ +{ + "values": [] +} \ No newline at end of file diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java index 8f66013fcf..e0948b0fee 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -26,6 +26,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Holder; import net.minecraft.core.HolderLookup; +import net.minecraft.core.HolderSet; import net.minecraft.core.RegistryCodecs; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.Registries; @@ -115,6 +116,7 @@ import net.neoforged.neoforge.common.data.internal.NeoForgeItemTagsProvider; import net.neoforged.neoforge.common.data.internal.NeoForgeLanguageProvider; import net.neoforged.neoforge.common.data.internal.NeoForgeLootTableProvider; +import net.neoforged.neoforge.common.data.internal.NeoForgeMobEffectsTagsProvider; import net.neoforged.neoforge.common.data.internal.NeoForgeRecipeProvider; import net.neoforged.neoforge.common.data.internal.NeoForgeRegistryOrderReportProvider; import net.neoforged.neoforge.common.data.internal.NeoForgeSpriteSourceProvider; @@ -161,6 +163,7 @@ import net.neoforged.neoforge.registries.holdersets.AndHolderSet; import net.neoforged.neoforge.registries.holdersets.AnyHolderSet; import net.neoforged.neoforge.registries.holdersets.HolderSetType; +import net.neoforged.neoforge.registries.holdersets.LazyNamedHolderSet; import net.neoforged.neoforge.registries.holdersets.NotHolderSet; import net.neoforged.neoforge.registries.holdersets.OrHolderSet; import net.neoforged.neoforge.server.command.EnumArgument; @@ -353,6 +356,12 @@ public class NeoForgeMod { */ public static final Holder NOT_HOLDER_SET = HOLDER_SET_TYPES.register("not", NotHolderSet.Type::new); + /** + * Stock holder set type that represents the values of the tag it is constructed from. Intended for cases where a tag needs to be specified as a holder set in a context + * where directly getting a {@link HolderSet.Named} from a registry is not possible. + */ + public static final Holder LAZY_NAMED_HOLDER_SET = HOLDER_SET_TYPES.register("lazy_named", LazyNamedHolderSet.Type::new); + private static final DeferredRegister> INGREDIENT_TYPES = DeferredRegister.create(NeoForgeRegistries.Keys.INGREDIENT_TYPES, NeoForgeVersion.MOD_ID); public static final DeferredHolder, IngredientType> COMPOUND_INGREDIENT_TYPE = INGREDIENT_TYPES.register("compound", () -> new IngredientType<>(CompoundIngredient.CODEC)); @@ -626,6 +635,7 @@ public void gatherData(GatherDataEvent event) { gen.addProvider(event.includeServer(), new NeoForgeDamageTypeTagsProvider(packOutput, lookupProvider, existingFileHelper)); gen.addProvider(event.includeServer(), new NeoForgeRegistryOrderReportProvider(packOutput)); gen.addProvider(event.includeServer(), new NeoForgeDataMapsProvider(packOutput, lookupProvider)); + gen.addProvider(event.includeServer(), new NeoForgeMobEffectsTagsProvider(packOutput, lookupProvider, existingFileHelper)); gen.addProvider(event.includeClient(), new NeoForgeSpriteSourceProvider(packOutput, lookupProvider, existingFileHelper)); gen.addProvider(event.includeClient(), new VanillaSoundDefinitionsProvider(packOutput, existingFileHelper)); diff --git a/src/main/java/net/neoforged/neoforge/common/Tags.java b/src/main/java/net/neoforged/neoforge/common/Tags.java index 8a57785dc8..c54b842767 100644 --- a/src/main/java/net/neoforged/neoforge/common/Tags.java +++ b/src/main/java/net/neoforged/neoforge/common/Tags.java @@ -14,6 +14,7 @@ import net.minecraft.tags.ItemTags; import net.minecraft.tags.TagKey; import net.minecraft.world.damagesource.DamageType; +import net.minecraft.world.effect.MobEffect; import net.minecraft.world.entity.EntityType; import net.minecraft.world.item.DyeColor; import net.minecraft.world.item.Item; @@ -1162,6 +1163,16 @@ private static TagKey neoforgeTag(String name) { } } + public static class MobEffects { + public static final TagKey NOT_MILK_CURABLE = neoforgeTag("not_milk_curable"); + + public static final TagKey NOT_TOTEM_CURABLE = neoforgeTag("not_totem_curable"); + + private static TagKey neoforgeTag(String name) { + return TagKey.create(Registries.MOB_EFFECT, ResourceLocation.fromNamespaceAndPath("neoforge", name)); + } + } + /** * Use this to get a TagKey's translation key safely on any side. * diff --git a/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeMobEffectsTagsProvider.java b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeMobEffectsTagsProvider.java new file mode 100644 index 0000000000..434cc499af --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/data/internal/NeoForgeMobEffectsTagsProvider.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.data.internal; + +import java.util.concurrent.CompletableFuture; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.PackOutput; +import net.minecraft.data.tags.TagsProvider; +import net.minecraft.world.effect.MobEffect; +import net.neoforged.neoforge.common.Tags; +import net.neoforged.neoforge.common.data.ExistingFileHelper; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.Nullable; + +public final class NeoForgeMobEffectsTagsProvider extends TagsProvider { + public NeoForgeMobEffectsTagsProvider(PackOutput output, CompletableFuture lookupProvider, @Nullable ExistingFileHelper fileHelper) { + super(output, Registries.MOB_EFFECT, lookupProvider, NeoForgeVersion.MOD_ID, fileHelper); + } + + @Override + protected void addTags(HolderLookup.Provider lookupProvider) { + tag(Tags.MobEffects.NOT_MILK_CURABLE); + tag(Tags.MobEffects.NOT_TOTEM_CURABLE); + } +} diff --git a/src/main/java/net/neoforged/neoforge/registries/holdersets/LazyNamedHolderSet.java b/src/main/java/net/neoforged/neoforge/registries/holdersets/LazyNamedHolderSet.java new file mode 100644 index 0000000000..01f834e8fa --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/registries/holdersets/LazyNamedHolderSet.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.registries.holdersets; + +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import net.minecraft.core.Holder; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.HolderOwner; +import net.minecraft.core.HolderSet; +import net.minecraft.core.Registry; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceKey; +import net.minecraft.tags.TagKey; +import net.minecraft.util.RandomSource; +import net.neoforged.neoforge.common.NeoForgeMod; +import org.jetbrains.annotations.Nullable; + +public final class LazyNamedHolderSet implements ICustomHolderSet { + private final List owners = new ArrayList<>(); + private final HolderLookup.RegistryLookup registryLookup; + private final TagKey tagKey; + @Nullable + private HolderSet.Named resolvedSet; + + public LazyNamedHolderSet(HolderLookup.RegistryLookup registryLookup, TagKey tagKey) { + this.registryLookup = registryLookup; + this.tagKey = tagKey; + } + + public HolderLookup.RegistryLookup registryLookup() { + return registryLookup; + } + + public TagKey tagKey() { + return tagKey; + } + + @Override + public HolderSetType type() { + return NeoForgeMod.LAZY_NAMED_HOLDER_SET.value(); + } + + @Override + public void addInvalidationListener(Runnable runnable) { + this.owners.add(runnable); + } + + @Override + public Stream> stream() { + return resolve().stream(); + } + + @Override + public int size() { + return resolve().size(); + } + + @Override + public boolean isBound() { + return resolve().isBound(); + } + + @Override + public Either, List>> unwrap() { + return resolve().unwrap(); + } + + @Override + public Optional> getRandomElement(RandomSource random) { + return resolve().getRandomElement(random); + } + + @Override + public Holder get(int idx) { + return resolve().get(idx); + } + + @Override + public boolean contains(Holder holder) { + return resolve().contains(holder); + } + + @Override + public boolean canSerializeIn(HolderOwner owner) { + return resolve().canSerializeIn(owner); + } + + @Override + public Optional> unwrapKey() { + return resolve().unwrapKey(); + } + + @Override + public Iterator> iterator() { + return resolve().iterator(); + } + + @Override + public String toString() { + return "LazyNamedHolderSet(" + this.tagKey + ")"; + } + + private HolderSet.Named resolve() { + HolderSet.Named resolved = this.resolvedSet; + if (resolved == null) { + this.resolvedSet = resolved = this.registryLookup.getOrThrow(this.tagKey); + resolved.addInvalidationListener(this::invalidate); + } + return resolved; + } + + private void invalidate() { + this.resolvedSet = null; + for (Runnable runnable : this.owners) { + runnable.run(); + } + } + + public static final class Type implements HolderSetType { + @Override + public MapCodec> makeCodec(ResourceKey> registryKey, Codec> holderCodec, boolean forceList) { + return RecordCodecBuilder.>mapCodec( + builder -> builder.group( + RegistryOps.retrieveRegistryLookup(registryKey).forGetter(LazyNamedHolderSet::registryLookup), + TagKey.hashedCodec(registryKey).fieldOf("tag").forGetter(LazyNamedHolderSet::tagKey)) + .apply(builder, LazyNamedHolderSet::new)); + } + + @Override + public StreamCodec> makeStreamCodec(ResourceKey> registryKey) { + return new StreamCodec<>() { + private final StreamCodec> keyCodec = TagKey.streamCodec(registryKey); + + @Override + public LazyNamedHolderSet decode(RegistryFriendlyByteBuf buf) { + return new LazyNamedHolderSet<>(buf.registryAccess().lookupOrThrow(registryKey), keyCodec.decode(buf)); + } + + @Override + public void encode(RegistryFriendlyByteBuf buf, LazyNamedHolderSet set) { + keyCodec.encode(buf, set.tagKey); + } + }; + } + } +} diff --git a/tests/src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json b/tests/src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json new file mode 100644 index 0000000000..52db8d62bb --- /dev/null +++ b/tests/src/generated/resources/data/neoforge/tags/mob_effect/not_milk_curable.json @@ -0,0 +1,5 @@ +{ + "values": [ + "neotests_effect_cures:test_effect" + ] +} \ No newline at end of file diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/effect/MobEffectTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/effect/MobEffectTests.java index d3d7b4059d..f5cd52d532 100644 --- a/tests/src/main/java/net/neoforged/neoforge/debug/effect/MobEffectTests.java +++ b/tests/src/main/java/net/neoforged/neoforge/debug/effect/MobEffectTests.java @@ -3,10 +3,33 @@ * SPDX-License-Identifier: LGPL-2.1-only */ -// FIXME: effect cures need to be implemented differently and the test adapted -/* package net.neoforged.neoforge.debug.effect; +import java.util.concurrent.CompletableFuture; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.HolderSet; +import net.minecraft.core.registries.Registries; +import net.minecraft.data.PackOutput; +import net.minecraft.data.tags.TagsProvider; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.animal.Pig; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.component.Consumables; +import net.minecraft.world.item.consume_effects.RemoveStatusEffectsConsumeEffect; +import net.neoforged.neoforge.common.Tags; +import net.neoforged.neoforge.common.data.ExistingFileHelper; +import net.neoforged.neoforge.data.event.GatherDataEvent; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.testframework.DynamicTest; +import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.registration.RegistrationHelper; @ForEachTest(groups = MobEffectTests.GROUP) public class MobEffectTests { @@ -16,23 +39,19 @@ public class MobEffectTests { @EmptyTemplate @TestHolder(description = "Tests whether items and effects can properly specify what they cure and what they are cured by respectively") static void effectCures(final DynamicTest test, final RegistrationHelper reg) { - final var testCure = EffectCure.get("test_cure"); - final var testCureTwo = EffectCure.get("test_cure_two"); + final var testEffect = reg.registrar(Registries.MOB_EFFECT).register("test_effect", () -> new MobEffect(MobEffectCategory.HARMFUL, 0xFF0000) {}); - final var testEffect = reg.registrar(Registries.MOB_EFFECT).register("test_effect", () -> new MobEffect( - MobEffectCategory.HARMFUL, 0xFF0000) { - @Override - public void fillEffectCures(Set cures, MobEffectInstance effectInstance) { - super.fillEffectCures(cures, effectInstance); - cures.remove(EffectCures.MILK); - cures.add(testCureTwo); - } - }); + test.framework().modEventBus().addListener(GatherDataEvent.class, event -> { + PackOutput output = event.getGenerator().getPackOutput(); + CompletableFuture lookupProvider = event.getLookupProvider(); + ExistingFileHelper fileHelper = event.getExistingFileHelper(); - test.eventListeners().forge().addListener((MobEffectEvent.Added event) -> { - if (event.getEffectInstance().getEffect() == MobEffects.NIGHT_VISION) { - event.getEffectInstance().getCures().add(testCure); - } + event.getGenerator().addProvider(event.includeServer(), new TagsProvider(output, Registries.MOB_EFFECT, lookupProvider, NeoForgeVersion.MOD_ID, fileHelper) { + @Override + protected void addTags(HolderLookup.Provider registries) { + tag(Tags.MobEffects.NOT_MILK_CURABLE).add(testEffect.unwrapKey().orElseThrow()); + } + }); }); test.onGameTest(helper -> { @@ -40,37 +59,24 @@ public void fillEffectCures(Set cures, MobEffectInstance effectInsta pig.addEffect(new MobEffectInstance(MobEffects.CONFUSION)); helper.assertMobEffectPresent(pig, MobEffects.CONFUSION, "'confusion was applied'"); - pig.removeEffectsCuredBy(testCure); - helper.assertMobEffectPresent(pig, MobEffects.CONFUSION, "'confusion not removed by test cure'"); - pig.removeEffectsCuredBy(EffectCures.MILK); + new RemoveStatusEffectsConsumeEffect(MobEffects.BLINDNESS).apply(pig.level(), ItemStack.EMPTY, pig); + helper.assertMobEffectPresent(pig, MobEffects.CONFUSION, "'confusion not removed by blindness cure'"); + Consumables.MILK_BUCKET.onConsume(pig.level(), pig, ItemStack.EMPTY); helper.assertMobEffectAbsent(pig, MobEffects.CONFUSION, "'confusion removed by milk'"); pig.addEffect(new MobEffectInstance(MobEffects.NIGHT_VISION)); helper.assertMobEffectPresent(pig, MobEffects.NIGHT_VISION, "'nightvision was applied'"); - pig.removeEffectsCuredBy(testCure); - helper.assertMobEffectAbsent(pig, MobEffects.NIGHT_VISION, "'nightvision removed by test cure'"); + new RemoveStatusEffectsConsumeEffect(HolderSet.direct(MobEffects.NIGHT_VISION)).apply(pig.level(), ItemStack.EMPTY, pig); + helper.assertMobEffectAbsent(pig, MobEffects.NIGHT_VISION, "'nightvision removed by nightvision cure'"); pig.addEffect(new MobEffectInstance(testEffect)); helper.assertMobEffectPresent(pig, testEffect, "'test effect was applied'"); - pig.removeEffectsCuredBy(EffectCures.MILK); + Consumables.MILK_BUCKET.onConsume(pig.level(), pig, ItemStack.EMPTY); helper.assertMobEffectPresent(pig, testEffect, "'test effect not removed by milk'"); - pig.removeEffectsCuredBy(testCureTwo); - helper.assertMobEffectAbsent(pig, testEffect, "'test effect removed by test cure'"); - - MobEffectInstance srcInst = new MobEffectInstance(MobEffects.CONFUSION); - MobEffectInstance destInst = MobEffectInstance.load((CompoundTag) srcInst.save()); - helper.assertTrue(srcInst.getCures().equals(destInst.getCures()), "'MobEffectInstance serialization roundtrip (standard cures)'"); - - srcInst.getCures().add(testCure); - destInst = MobEffectInstance.load((CompoundTag) srcInst.save()); - helper.assertTrue(srcInst.getCures().equals(destInst.getCures()), "'MobEffectInstance serialization roundtrip (custom additional cure)'"); - - srcInst.getCures().clear(); - destInst = MobEffectInstance.load((CompoundTag) srcInst.save()); - helper.assertTrue(srcInst.getCures().equals(destInst.getCures()), "'MobEffectInstance serialization roundtrip (no cures)'"); + new RemoveStatusEffectsConsumeEffect(testEffect).apply(pig.level(), ItemStack.EMPTY, pig); + helper.assertMobEffectAbsent(pig, testEffect, "'test effect removed by test effect cure'"); helper.succeed(); }); } } -*/