diff --git a/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/QuiltLivingEntityStatusEffectExtensions.java b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/QuiltLivingEntityStatusEffectExtensions.java index 573d48561..87a79689f 100644 --- a/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/QuiltLivingEntityStatusEffectExtensions.java +++ b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/QuiltLivingEntityStatusEffectExtensions.java @@ -42,7 +42,7 @@ default boolean removeStatusEffect(@NotNull Holder type, @NotNull } /** - * Removes all status effects. + * Removes the status effects that {@link StatusEffectRemovalReason#removesEffect(StatusEffectInstance)} returns {@code true} for. * * @param reason the reason to remove the status effects * @return the number of status effects that were successfully removed. diff --git a/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/StatusEffectRemovalReason.java b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/StatusEffectRemovalReason.java index 4c3a1503f..027aaa607 100644 --- a/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/StatusEffectRemovalReason.java +++ b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/api/StatusEffectRemovalReason.java @@ -21,6 +21,8 @@ import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffect; import net.minecraft.entity.effect.StatusEffectInstance; +import net.minecraft.item.ItemStack; +import net.minecraft.item.RemoveEffectsComponent; import net.minecraft.util.Identifier; import org.quiltmc.qsl.entity.effect.impl.QuiltStatusEffectInternals; @@ -74,10 +76,14 @@ public class StatusEffectRemovalReason { new StatusEffectRemovalReason(QuiltStatusEffectInternals.id("command.one")); /** - * Used when effects are removed via drinking milk. Does not have to be the vanilla milk bucket. + * The identifier used when effects are removed via the {@link net.minecraft.item.ClearAllEffectsComponent}, such as a milk bucket. */ - public static final StatusEffectRemovalReason DRANK_MILK = - new StatusEffectRemovalReason(QuiltStatusEffectInternals.id("action.drank_milk")); + public static final Identifier CLEAR_ALL_EFFECTS_COMPONENT_ID = QuiltStatusEffectInternals.id("action.consume.clear_all_effects"); + + /** + * The identifier used when effects are removed via the {@link net.minecraft.item.RemoveEffectsComponent}, such as poison with honey bottles. + */ + public static final Identifier REMOVE_EFFECTS_COMPONENT_ID = QuiltStatusEffectInternals.id("action.consume.remove_effects"); protected final @NotNull Identifier id; @@ -110,4 +116,37 @@ public StatusEffectRemovalReason(@NotNull Identifier id) { public boolean removesEffect(StatusEffectInstance effect) { return true; } + + /** + * A removal reason that stems from a consumption action that has an associated {@link ItemStack}. + */ + public static class ConsumeRemovalReason extends StatusEffectRemovalReason { + private final ItemStack stack; + + public ConsumeRemovalReason(@NotNull Identifier id, ItemStack stack) { + super(id); + this.stack = stack; + } + + public ItemStack stack() { + return this.stack; + } + } + + /** + * A specific removal for the {@link RemoveEffectsComponent}. + */ + public static class RemoveEffectsComponentReason extends ConsumeRemovalReason { + private final net.minecraft.item.RemoveEffectsComponent component; + + public RemoveEffectsComponentReason(ItemStack stack, net.minecraft.item.RemoveEffectsComponent component) { + super(REMOVE_EFFECTS_COMPONENT_ID, stack); + this.component = component; + } + + @Override + public boolean removesEffect(StatusEffectInstance effect) { + return this.component.effects().contains(effect.getEffectType()); + } + } } diff --git a/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/mixin/ClearAllEffectsComponentMixin.java b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/mixin/ClearAllEffectsComponentMixin.java index 9714a68a2..a8c4dc029 100644 --- a/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/mixin/ClearAllEffectsComponentMixin.java +++ b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/mixin/ClearAllEffectsComponentMixin.java @@ -16,15 +16,15 @@ package org.quiltmc.qsl.entity.effect.mixin; +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; 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.CallbackInfoReturnable; import net.minecraft.item.ClearAllEffectsComponent; +import net.minecraft.entity.LivingEntity; import net.minecraft.item.ItemStack; import net.minecraft.world.World; -import net.minecraft.entity.LivingEntity; import org.quiltmc.qsl.entity.effect.api.StatusEffectRemovalReason; import org.quiltmc.qsl.entity.effect.impl.QuiltStatusEffectInternals; @@ -32,10 +32,12 @@ // See LivingEntityMixin @Mixin(value = ClearAllEffectsComponent.class, priority = QuiltStatusEffectInternals.MIXIN_PRIORITY) public abstract class ClearAllEffectsComponentMixin { - @Inject(method = "apply", at = @At(value = "HEAD")) - private void quilt$addRemovalReason( - World world, ItemStack stack, LivingEntity entity, CallbackInfoReturnable cir + @WrapOperation(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;clearStatusEffects()Z")) + private boolean quilt$addRemovalReason( + LivingEntity instance, Operation original, World world, ItemStack stack ) { - entity.clearStatusEffects(StatusEffectRemovalReason.DRANK_MILK); + return instance.clearStatusEffects( + new StatusEffectRemovalReason.ConsumeRemovalReason(StatusEffectRemovalReason.CLEAR_ALL_EFFECTS_COMPONENT_ID, stack) + ) > 0; } } diff --git a/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/mixin/RemoveEffectsComponentMixin.java b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/mixin/RemoveEffectsComponentMixin.java new file mode 100644 index 000000000..184624f1d --- /dev/null +++ b/library/entity/status_effect/src/main/java/org/quiltmc/qsl/entity/effect/mixin/RemoveEffectsComponentMixin.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 The Quilt Project + * + * 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 org.quiltmc.qsl.entity.effect.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import net.minecraft.entity.LivingEntity; +import net.minecraft.entity.effect.StatusEffect; +import net.minecraft.item.ItemStack; +import net.minecraft.item.RemoveEffectsComponent; +import net.minecraft.registry.Holder; +import net.minecraft.world.World; + +import org.quiltmc.qsl.entity.effect.api.StatusEffectRemovalReason; +import org.quiltmc.qsl.entity.effect.impl.QuiltStatusEffectInternals; + +// See LivingEntityMixin +@Mixin(value = RemoveEffectsComponent.class, priority = QuiltStatusEffectInternals.MIXIN_PRIORITY) +public abstract class RemoveEffectsComponentMixin { + @WrapOperation(method = "apply", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/LivingEntity;removeStatusEffect(Lnet/minecraft/registry/Holder;)Z")) + private boolean quilt$addRemovalReason( + LivingEntity instance, Holder effect, Operation original, World world, ItemStack stack + ) { + return instance.removeStatusEffect( + effect, new StatusEffectRemovalReason.RemoveEffectsComponentReason(stack, ((RemoveEffectsComponent) (Object) this)) + ); + } +} diff --git a/library/entity/status_effect/src/main/resources/quilt_status_effect.mixins.json b/library/entity/status_effect/src/main/resources/quilt_status_effect.mixins.json index 5f8d3408a..c9b779b8c 100644 --- a/library/entity/status_effect/src/main/resources/quilt_status_effect.mixins.json +++ b/library/entity/status_effect/src/main/resources/quilt_status_effect.mixins.json @@ -6,6 +6,7 @@ "ClearAllEffectsComponentMixin", "EffectCommandMixin", "LivingEntityMixin", + "RemoveEffectsComponentMixin", "StatusEffectAccessor", "StatusEffectMixin" ], diff --git a/library/entity/status_effect/src/testmod/java/org/quiltmc/qsl/entity/effect/test/PasteurizedMilkBucketItem.java b/library/entity/status_effect/src/testmod/java/org/quiltmc/qsl/entity/effect/test/PasteurizedMilkBucketItem.java deleted file mode 100644 index 2813a02fe..000000000 --- a/library/entity/status_effect/src/testmod/java/org/quiltmc/qsl/entity/effect/test/PasteurizedMilkBucketItem.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2022 The Quilt Project - * - * 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 org.quiltmc.qsl.entity.effect.test; - -import net.minecraft.advancement.criterion.Criteria; -import net.minecraft.entity.LivingEntity; -import net.minecraft.entity.player.PlayerEntity; -import net.minecraft.item.Item; -import net.minecraft.item.ItemStack; -import net.minecraft.item.ItemUsage; -import net.minecraft.item.Items; -import net.minecraft.item.UseAnimation; -import net.minecraft.server.network.ServerPlayerEntity; -import net.minecraft.stat.Stats; -import net.minecraft.util.ActionResult; -import net.minecraft.util.Hand; -import net.minecraft.world.World; - -public final class PasteurizedMilkBucketItem extends Item { - private static final int MAX_USE_TIME = 32; - - public PasteurizedMilkBucketItem(Settings settings) { - super(settings); - } - - @Override - public ItemStack finishUsing(ItemStack stack, World world, LivingEntity user) { - if (user instanceof ServerPlayerEntity player) { - Criteria.CONSUME_ITEM.trigger(player, stack); - player.incrementStat(Stats.USED.getOrCreateStat(this)); - } - - if (user instanceof PlayerEntity player && !player.getAbilities().creativeMode) { - stack.decrement(1); - } - - if (!world.isClient) { - user.clearStatusEffects(StatusEffectTest.DRANK_PASTEURIZED_MILK); - } - - return stack.isEmpty() ? new ItemStack(Items.BUCKET) : stack; - } - - @Override - public int getUseTicks(ItemStack stack, LivingEntity entity) { - return MAX_USE_TIME; - } - - @Override - public UseAnimation getUseAction(ItemStack stack) { - return UseAnimation.DRINK; - } - - @Override - public ActionResult use(World world, PlayerEntity user, Hand hand) { - return ItemUsage.consumeHeldItem(world, user, hand); - } -} diff --git a/library/entity/status_effect/src/testmod/java/org/quiltmc/qsl/entity/effect/test/StatusEffectTest.java b/library/entity/status_effect/src/testmod/java/org/quiltmc/qsl/entity/effect/test/StatusEffectTest.java index 6534024f2..b620b43d7 100644 --- a/library/entity/status_effect/src/testmod/java/org/quiltmc/qsl/entity/effect/test/StatusEffectTest.java +++ b/library/entity/status_effect/src/testmod/java/org/quiltmc/qsl/entity/effect/test/StatusEffectTest.java @@ -16,18 +16,31 @@ package org.quiltmc.qsl.entity.effect.test; +import com.mojang.serialization.MapCodec; + +import net.minecraft.component.DataComponentTypes; +import net.minecraft.component.type.ConsumableComponent; +import net.minecraft.entity.LivingEntity; import net.minecraft.entity.effect.StatusEffectInstance; import net.minecraft.entity.effect.StatusEffectType; +import net.minecraft.item.ConsumeEffect; import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; import net.minecraft.item.Items; +import net.minecraft.item.UseAnimation; +import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.codec.PacketCodec; import net.minecraft.registry.Registries; import net.minecraft.registry.Registry; import net.minecraft.registry.RegistryKey; import net.minecraft.registry.RegistryKeys; +import net.minecraft.sound.SoundEvents; import net.minecraft.util.Identifier; +import net.minecraft.world.World; import org.quiltmc.loader.api.ModContainer; import org.quiltmc.qsl.base.api.entrypoint.ModInitializer; +import org.quiltmc.qsl.entity.effect.api.StatusEffectEvents; import org.quiltmc.qsl.entity.effect.api.StatusEffectRemovalReason; public final class StatusEffectTest implements ModInitializer { @@ -37,22 +50,67 @@ public static Identifier createId(String path) { return Identifier.of(NAMESPACE, path); } - public static final StatusEffectRemovalReason DRANK_PASTEURIZED_MILK = - new StatusEffectRemovalReason(createId("action.drank_pasteurized_milk")) { - @Override - public boolean removesEffect(StatusEffectInstance effect) { - return effect.getEffectType().getValue().getType() == StatusEffectType.HARMFUL; - } - }; + public static class DrankPasteurizedMilkRemovalReason extends StatusEffectRemovalReason.ConsumeRemovalReason { + public static final Identifier DRANK_PASTEURIZED_MILK_ID = createId("action.consume.drank_pasteurized_milk"); + public DrankPasteurizedMilkRemovalReason(ItemStack stack) { + super(DRANK_PASTEURIZED_MILK_ID, stack); + } + + @Override + public boolean removesEffect(StatusEffectInstance effect) { + return effect.getEffectType().getValue().getType() == StatusEffectType.HARMFUL; + } + } + + public record DrankPasteurizedMilk() implements ConsumeEffect { + public static final ConsumeEffect.Type DRANK_PASTEURIZED_MILK = Registry.register( + Registries.CONSUME_EFFECT_TYPE, + createId("drank_pasteurized_milk"), + new ConsumeEffect.Type<>(DrankPasteurizedMilk.CODEC, DrankPasteurizedMilk.PACKET_CODEC) + ); + + public static final DrankPasteurizedMilk INSTANCE = new DrankPasteurizedMilk(); + public static final MapCodec CODEC = MapCodec.unit(INSTANCE); + public static final PacketCodec PACKET_CODEC = PacketCodec.unit(INSTANCE); + + @Override + public Type getType() { + return DRANK_PASTEURIZED_MILK; + } + + @Override + public boolean apply(World world, ItemStack stack, LivingEntity entity) { + return entity.clearStatusEffects(new DrankPasteurizedMilkRemovalReason(stack)) > 0; + } + } @Override public void onInitialize(ModContainer mod) { RegistryKey bucketKey = RegistryKey.of(RegistryKeys.ITEM, createId("pasteurized_milk_bucket")); - Registry.register(Registries.ITEM, bucketKey, new PasteurizedMilkBucketItem( + Registry.register(Registries.ITEM, bucketKey, new Item( new Item.Settings() .key(bucketKey) .recipeRemainder(Items.BUCKET) + .useRemainder(Items.BUCKET) .maxCount(1) + .component(DataComponentTypes.CONSUMABLE, + ConsumableComponent.builder() + .consumeSeconds(1.6F) + .animation(UseAnimation.DRINK) + .sound(SoundEvents.ENTITY_GENERIC_DRINK) + .hasConsumeParticles(false) + .effect(DrankPasteurizedMilk.INSTANCE) + .build()) )); + + StatusEffectEvents.ON_REMOVED.register((entity, effect, reason) -> { + if (reason instanceof DrankPasteurizedMilkRemovalReason milk) { + System.out.println("Removed " + effect.getTranslationKey() + " with pasteurized milk{" + milk.stack().toString() + "}!"); + } else if (reason instanceof StatusEffectRemovalReason.ConsumeRemovalReason consume) { + System.out.println("Removed " + effect.getTranslationKey() + " with consume reason " + consume.getId() + "{" + consume.stack().toString() + "}!"); + } else { + System.out.println("Removed " + effect.getTranslationKey() + " with reason " + reason.getId()); + } + }); } }