diff --git a/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/event/common/PotionEvents.java b/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/event/common/PotionEvents.java index fa976c45..9aa6fb49 100644 --- a/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/event/common/PotionEvents.java +++ b/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/event/common/PotionEvents.java @@ -2,6 +2,7 @@ import javax.annotation.Nullable; +import io.github.fabricators_of_create.porting_lib.entity.events.living.MobEffectEvent; import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.EventFactory; import net.minecraft.world.InteractionResult; @@ -9,6 +10,10 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; +/** + * Use {@link MobEffectEvent} + */ +@Deprecated(forRemoval = true) public class PotionEvents { public static Event POTION_ADDED = EventFactory.createArrayBacked(PotionAdded.class, callbacks -> (entity, newEffect, oldEffect, source) -> { for (PotionAdded e : callbacks) diff --git a/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/util/PotionHelper.java b/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/util/PotionHelper.java index 6a432aa5..f2672ac6 100644 --- a/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/util/PotionHelper.java +++ b/modules/base/src/main/java/io/github/fabricators_of_create/porting_lib/util/PotionHelper.java @@ -2,6 +2,7 @@ import java.util.Iterator; +import io.github.fabricators_of_create.porting_lib.entity.events.living.MobEffectEvent; import io.github.fabricators_of_create.porting_lib.mixin.accessors.common.accessor.LivingEntityAccessor; import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.entity.LivingEntity; @@ -19,7 +20,7 @@ public static boolean curePotionEffects(LivingEntity livingEntity, ItemStack cur Iterator itr = livingEntity.getActiveEffects().iterator(); while (itr.hasNext()) { MobEffectInstance effect = itr.next(); - if (effect.isCurativeItem(curativeItem) /*&& !net.minecraftforge.common.MinecraftForge.EVENT_BUS.post(new net.minecraftforge.event.entity.living.PotionEvent.PotionRemoveEvent(this, effect))*/) { + if (effect.isCurativeItem(curativeItem) && !new MobEffectEvent.Remove(livingEntity, effect).post()) { ((LivingEntityAccessor)livingEntity).port_lib$onEffectRemoved(effect); itr.remove(); ret = true; diff --git a/modules/core/src/main/java/io/github/fabricators_of_create/porting_lib/core/event/BaseEvent.java b/modules/core/src/main/java/io/github/fabricators_of_create/porting_lib/core/event/BaseEvent.java index ab6812de..fe81cbe0 100644 --- a/modules/core/src/main/java/io/github/fabricators_of_create/porting_lib/core/event/BaseEvent.java +++ b/modules/core/src/main/java/io/github/fabricators_of_create/porting_lib/core/event/BaseEvent.java @@ -46,4 +46,9 @@ public boolean isCanceled() { } public abstract void sendEvent(); + + public boolean post() { + sendEvent(); + return isCanceled(); + } } diff --git a/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/events/living/MobEffectEvent.java b/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/events/living/MobEffectEvent.java new file mode 100644 index 00000000..43f9c0fc --- /dev/null +++ b/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/events/living/MobEffectEvent.java @@ -0,0 +1,192 @@ +package io.github.fabricators_of_create.porting_lib.entity.events.living; + +import io.github.fabricators_of_create.porting_lib.entity.events.LivingEntityEvents; +import net.fabricmc.fabric.api.event.Event; +import net.fabricmc.fabric.api.event.EventFactory; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * This event is fired when an interaction between a {@link LivingEntity} and {@link MobEffectInstance} happens. + */ +public abstract class MobEffectEvent extends LivingEntityEvents { + public static final Event REMOVE = EventFactory.createArrayBacked(RemoveCallback.class, callbacks -> event -> { + for (RemoveCallback c : callbacks) + c.onEffectRemove(event); + }); + public static final Event APPLICABLE = EventFactory.createArrayBacked(ApplicableCallback.class, callbacks -> event -> { + for (ApplicableCallback c : callbacks) + c.onEffectApplicable(event); + }); + public static final Event ADDED = EventFactory.createArrayBacked(AddedCallback.class, callbacks -> event -> { + for (AddedCallback c : callbacks) + c.onEffectAdded(event); + }); + public static final Event EXPIRED = EventFactory.createArrayBacked(ExpiredCallback.class, callbacks -> event -> { + for (ExpiredCallback c : callbacks) + c.onEffectExpired(event); + }); + + @Nullable + protected final MobEffectInstance effectInstance; + + public MobEffectEvent(LivingEntity living, MobEffectInstance effectInstance) { + super(living); + this.effectInstance = effectInstance; + } + + @Nullable + public MobEffectInstance getEffectInstance() { + return effectInstance; + } + + /** + * This Event is fired when a {@link MobEffect} is about to get removed from an Entity. + * This Event is cancelable. If canceled, the effect will not be removed. + * This Event does not have a result. + */ + public static class Remove extends MobEffectEvent { + private final MobEffect effect; + + public Remove(LivingEntity living, MobEffect effect) { + super(living, living.getEffect(effect)); + this.effect = effect; + } + + public Remove(LivingEntity living, MobEffectInstance effectInstance) { + super(living, effectInstance); + this.effect = effectInstance.getEffect(); + } + + /** + * @return the {@link MobEffectEvent} which is being removed from the entity + */ + public MobEffect getEffect() { + return this.effect; + } + + /** + * @return the {@link MobEffectInstance}. In the remove event, this can be null if the entity does not have a {@link MobEffect} of the right type active. + */ + @Override + @Nullable + public MobEffectInstance getEffectInstance() { + return super.getEffectInstance(); + } + + @Override + public void sendEvent() { + REMOVE.invoker().onEffectRemove(this); + } + } + + /** + * This event is fired to check if a {@link MobEffectInstance} can be applied to an entity. + * This event is not cancelable. + * This event has a result. + *

+ * {@link Result#ALLOW ALLOW} will apply this mob effect. + * {@link Result#DENY DENY} will not apply this mob effect. + * {@link Result#DEFAULT DEFAULT} will run vanilla logic to determine if this mob effect is applicable in {@link LivingEntity#canBeAffected}. + */ + public static class Applicable extends MobEffectEvent { + public Applicable(LivingEntity living, @NotNull MobEffectInstance effectInstance) { + super(living, effectInstance); + } + + @Override + @NotNull + public MobEffectInstance getEffectInstance() { + return super.getEffectInstance(); + } + + @Override + public void sendEvent() { + APPLICABLE.invoker().onEffectApplicable(this); + } + } + + /** + * This event is fired when a new {@link MobEffectInstance} is added to an entity. + * This event is also fired if an entity already has the effect but with a different duration or amplifier. + * This event is not cancelable. + * This event does not have a result. + */ + public static class Added extends MobEffectEvent { + private final MobEffectInstance oldEffectInstance; + private final Entity source; + + public Added(LivingEntity living, MobEffectInstance oldEffectInstance, MobEffectInstance newEffectInstance, Entity source) { + super(living, newEffectInstance); + this.oldEffectInstance = oldEffectInstance; + this.source = source; + } + + /** + * @return the added {@link MobEffectInstance}. This is the unmerged MobEffectInstance if the old MobEffectInstance is not null. + */ + @Override + @NotNull + public MobEffectInstance getEffectInstance() { + return super.getEffectInstance(); + } + + /** + * @return the old {@link MobEffectInstance}. This can be null if the entity did not have an effect of this kind before. + */ + @Nullable + public MobEffectInstance getOldEffectInstance() { + return oldEffectInstance; + } + + /** + * @return the entity source of the effect, or {@code null} if none exists + */ + @Nullable + public Entity getEffectSource() { + return source; + } + + @Override + public void sendEvent() { + ADDED.invoker().onEffectAdded(this); + } + } + + /** + * This event is fired when a {@link MobEffectInstance} expires on an entity. + * This event is not cancelable. + * This event does not have a result. + */ + public static class Expired extends MobEffectEvent { + public Expired(LivingEntity living, MobEffectInstance effectInstance) { + super(living, effectInstance); + } + + @Override + public void sendEvent() { + EXPIRED.invoker().onEffectExpired(this); + } + } + + public interface RemoveCallback { + void onEffectRemove(Remove event); + } + + public interface ApplicableCallback { + void onEffectApplicable(Applicable event); + } + + public interface AddedCallback { + void onEffectAdded(Added event); + } + + public interface ExpiredCallback { + void onEffectExpired(Expired event); + } +} diff --git a/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/mixin/common/LivingEntityMixin.java b/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/mixin/common/LivingEntityMixin.java index 53860cd1..6c3ad5b2 100644 --- a/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/mixin/common/LivingEntityMixin.java +++ b/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/mixin/common/LivingEntityMixin.java @@ -2,14 +2,17 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.llamalad7.mixinextras.injector.v2.WrapWithCondition; +import com.llamalad7.mixinextras.sugar.Local; import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import io.github.fabricators_of_create.porting_lib.core.event.BaseEvent; import io.github.fabricators_of_create.porting_lib.entity.events.LivingAttackEvent; import io.github.fabricators_of_create.porting_lib.entity.events.ShieldBlockEvent; @@ -17,7 +20,13 @@ import io.github.fabricators_of_create.porting_lib.entity.events.living.LivingHurtEvent; +import io.github.fabricators_of_create.porting_lib.entity.events.living.MobEffectEvent; +import net.minecraft.world.effect.MobEffect; + +import net.minecraft.world.effect.MobEffectInstance; + import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; @@ -253,4 +262,41 @@ private float livingDamageEvent(float value, DamageSource pDamageSource) { return 0; return event.getAmount(); } + + @Inject(method = "removeEffect", at = @At("HEAD"), cancellable = true) + private void onRemoveEffect(MobEffect effect, CallbackInfoReturnable cir) { + var event = new MobEffectEvent.Remove((LivingEntity) (Object) this, effect); + event.sendEvent(); + if (event.isCanceled()) cir.setReturnValue(false); + } + + @WrapWithCondition(method = "removeAllEffects", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/LivingEntity;onEffectRemoved(Lnet/minecraft/world/effect/MobEffectInstance;)V")) + private boolean onRemoveAllEffectEvent(LivingEntity instance, MobEffectInstance effect, @Share("event") LocalRef eventRef) { + var event = new MobEffectEvent.Remove(instance, effect); + eventRef.set(event); + event.sendEvent(); + return !event.isCanceled(); + } + + @WrapWithCondition(method = "removeAllEffects", at = @At(value = "INVOKE", target = "Ljava/util/Iterator;remove()V")) + private boolean skipRemove(Iterator iterator, @Share("event") LocalRef eventRef) { + return !eventRef.get().isCanceled(); + } + + @Inject(method = "canBeAffected", at = @At("HEAD"), cancellable = true) + private void isEffectApplicable(MobEffectInstance effectInstance, CallbackInfoReturnable cir) { + MobEffectEvent.Applicable event = new MobEffectEvent.Applicable((LivingEntity) (Object) this, effectInstance); + event.sendEvent(); + if (event.getResult() != BaseEvent.Result.DEFAULT) cir.setReturnValue(event.getResult() == BaseEvent.Result.ALLOW); + } + + @Inject(method = "addEffect(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)Z", at = @At(value = "JUMP", opcode = Opcodes.IFNONNULL)) + private void onEffectAdded(MobEffectInstance newEffect, Entity entity, CallbackInfoReturnable cir, @Local(index = 3) MobEffectInstance oldEffect) { + new MobEffectEvent.Added((LivingEntity) (Object) this, oldEffect, newEffect, entity).sendEvent(); + } + + @ModifyExpressionValue(method = "tickEffects", at = @At(value = "FIELD", target = "Lnet/minecraft/world/level/Level;isClientSide:Z", ordinal = 0)) + private boolean onEffectExpired(boolean original, @Local(index = 3) MobEffectInstance effect) { + return !(!original && !new MobEffectEvent.Expired((LivingEntity) (Object) this, effect).post()); + } } diff --git a/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/mixin/common/SpiderMixin.java b/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/mixin/common/SpiderMixin.java new file mode 100644 index 00000000..eab69f89 --- /dev/null +++ b/modules/entity/src/main/java/io/github/fabricators_of_create/porting_lib/entity/mixin/common/SpiderMixin.java @@ -0,0 +1,26 @@ +package io.github.fabricators_of_create.porting_lib.entity.mixin.common; + +import io.github.fabricators_of_create.porting_lib.core.event.BaseEvent; +import io.github.fabricators_of_create.porting_lib.entity.events.living.MobEffectEvent; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.effect.MobEffects; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.entity.monster.Spider; + +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; + +@Mixin(Spider.class) +public class SpiderMixin { + @Inject(method = "canBeAffected", at = @At("HEAD"), cancellable = true) + private void isEffectApplicable(MobEffectInstance effectInstance, CallbackInfoReturnable cir) { + if (effectInstance.getEffect() == MobEffects.POISON) { + MobEffectEvent.Applicable event = new MobEffectEvent.Applicable((LivingEntity) (Object) this, effectInstance); + event.sendEvent(); + if (event.getResult() != BaseEvent.Result.DEFAULT) + cir.setReturnValue(event.getResult() == BaseEvent.Result.ALLOW); + } + } +} diff --git a/modules/entity/src/main/resources/porting_lib_entity.mixins.json b/modules/entity/src/main/resources/porting_lib_entity.mixins.json index 20fbc17c..7eaf0687 100644 --- a/modules/entity/src/main/resources/porting_lib_entity.mixins.json +++ b/modules/entity/src/main/resources/porting_lib_entity.mixins.json @@ -43,6 +43,7 @@ "common.ServerPlayerMixin", "common.ShulkerBulletMixin", "common.SlimeMixin", + "common.SpiderMixin", "common.SpreadPlayersCommandMixin", "common.TeleportCommandMixin", "common.ThrowableProjectileMixin",