From 5650e9979ff931cc8e3813716e976716828563a5 Mon Sep 17 00:00:00 2001 From: fretuks Date: Wed, 26 Aug 2026 19:38:15 +0200 Subject: [PATCH] fix various issues throughout the entire project --- README.md | 2 +- .../net/fretux/ailments/AscendAilments.java | 5 +- .../net/fretux/ailments/api/AilmentApi.java | 54 ++++++++++---- .../fretux/ailments/compat/AscendCompat.java | 17 +++-- .../ailments/damage/ModDamageSources.java | 12 ++- .../fretux/ailments/effect/BleedEffect.java | 3 +- .../fretux/ailments/event/AilmentEvents.java | 34 +++++++-- .../ailments/util/ControlEffectHelper.java | 74 ++++++++++++++----- .../ailments/util/EffectSourceUtil.java | 13 ++++ .../ailments/util/HemorrhageTracker.java | 3 +- 10 files changed, 164 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 669c364..c72f2de 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Item tags: Direct melee hits with items in these tags apply their corresponding ailment. Soul Rot, Bleed, and Fracture use their configured stack/duration behavior; the other tags use `taggedWeaponEffectDurationTicks`. Bleed also applies to non-vanilla item registry paths containing a configured katana-family keyword. Automatic application, proc chance, matching keywords, -and the PvP proc cooldown are configurable under `integration` in `ascend-ailments-common.toml`. PvP cooldowns are +and the PvP proc cooldown are configurable under `integration` in `ascend-ailments-server.toml`. PvP cooldowns are tracked per attacking player and ailment; their configured minimum is 60 ticks (3 seconds). ## Admin commands diff --git a/src/main/java/net/fretux/ailments/AscendAilments.java b/src/main/java/net/fretux/ailments/AscendAilments.java index 33718b9..b41e264 100644 --- a/src/main/java/net/fretux/ailments/AscendAilments.java +++ b/src/main/java/net/fretux/ailments/AscendAilments.java @@ -25,8 +25,9 @@ public final class AscendAilments { ModEffects.register(modBus); AilmentNetwork.register(); modBus.addListener(this::commonSetup); - context.registerConfig(ModConfig.Type.COMMON, AilmentsConfig.SPEC, - "ascend-ailments-common.toml"); + // Gameplay settings belong to the server and are synchronized to multiplayer clients by Forge. + context.registerConfig(ModConfig.Type.SERVER, AilmentsConfig.SPEC, + "ascend-ailments-server.toml"); MinecraftForge.EVENT_BUS.addListener(this::registerCommands); } diff --git a/src/main/java/net/fretux/ailments/api/AilmentApi.java b/src/main/java/net/fretux/ailments/api/AilmentApi.java index 884d76b..e105ec7 100644 --- a/src/main/java/net/fretux/ailments/api/AilmentApi.java +++ b/src/main/java/net/fretux/ailments/api/AilmentApi.java @@ -1,6 +1,7 @@ package net.fretux.ailments.api; import net.fretux.ailments.config.AilmentsConfig; +import net.fretux.ailments.compat.AscendCompat; import net.fretux.ailments.registry.ModEffects; import net.fretux.ailments.util.EffectSourceUtil; import net.fretux.ailments.util.MentalControlUtil; @@ -117,8 +118,7 @@ public final class AilmentApi { int base = AilmentsConfig.value(AilmentsConfig.SOUL_ROT_DURATION); if (extendWithStacks) base = safeAdd(base, amplifier * 20); int duration = AilmentScaling.scaleDuration(base, source); - target.removeEffect(effect); - boolean applied = target.addEffect(instance(effect, duration, amplifier, true)); + boolean applied = addStackingEffect(target, source, effect, duration, amplifier, true); if (applied) { EffectSourceUtil.setSource(target, EffectSourceUtil.SOUL_ROT, source); EffectSourceUtil.setPotency(target, EffectSourceUtil.SOUL_ROT, @@ -148,8 +148,7 @@ public final class AilmentApi { : amplifier == 1 ? AilmentsConfig.value(AilmentsConfig.BLEED_DURATION_1) : AilmentsConfig.value(AilmentsConfig.BLEED_DURATION_2); int duration = AilmentScaling.scaleDuration(base, source); - target.removeEffect(effect); - boolean applied = target.addEffect(instance(effect, duration, amplifier, true)); + boolean applied = addStackingEffect(target, source, effect, duration, amplifier, true); if (applied) { EffectSourceUtil.setSource(target, EffectSourceUtil.BLEED, source); EffectSourceUtil.setPotency(target, EffectSourceUtil.BLEED, @@ -191,8 +190,7 @@ public final class AilmentApi { int stackedAmplifier = current == null ? 0 : Math.min(2, current.getAmplifier() + 1); int appliedAmplifier = Math.min(2, Math.max(stackedAmplifier, amplifier)); int duration = AilmentScaling.scaleDuration(requestedTicks, source); - target.removeEffect(effect); - boolean applied = target.addEffect(instance(effect, duration, appliedAmplifier, true)); + boolean applied = addStackingEffect(target, source, effect, duration, appliedAmplifier, true); if (applied) EffectSourceUtil.setSource(target, EffectSourceUtil.FRACTURE, source); return finishApplication(applied, target, source, AilmentType.FRACTURE); } @@ -233,12 +231,8 @@ public final class AilmentApi { public static boolean applyOvercharm(LivingEntity target, @Nullable LivingEntity source, int requestedTicks) { if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.OVERCHARM)) return false; int duration = AilmentScaling.scaleDuration(requestedTicks, source); - boolean result = target.addEffect(instance(ModEffects.OVERCHARM.get(), duration, 0, true)); + boolean result = addSourcedEffect(target, source, ModEffects.OVERCHARM.get(), duration, 0, true); if (result) { - // Ascend itself scales beneficial effects received by players. Restore the source-scaled duration so - // this API remains the single authority and cannot be scaled twice when both mods are installed. - MobEffectInstance active = target.getEffect(ModEffects.OVERCHARM.get()); - if (active != null) active.mapDuration(ignored -> duration); EffectSourceUtil.setSource(target, EffectSourceUtil.OVERCHARM, source); } return finishApplication(result, target, source, AilmentType.OVERCHARM); @@ -247,7 +241,7 @@ public final class AilmentApi { public static boolean applyWinded(LivingEntity target, @Nullable LivingEntity source, int requestedTicks) { if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.WINDED)) return false; int duration = AilmentScaling.scaleDuration(requestedTicks, source); - return finishApplication(target.addEffect(instance(ModEffects.WINDED.get(), duration, 0, true)), + return finishApplication(addSourcedEffect(target, source, ModEffects.WINDED.get(), duration, 0, true), target, source, AilmentType.WINDED); } @@ -311,15 +305,14 @@ public final class AilmentApi { private static boolean applySourced(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, String key, int duration, int amplifier) { - boolean applied = target.addEffect(instance(effect, Math.max(1, duration), amplifier, true)); + boolean applied = addSourcedEffect(target, source, effect, Math.max(1, duration), amplifier, true); if (applied) EffectSourceUtil.setSource(target, key, source); return applied; } private static boolean applyDot(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, String key, int requestedTicks, int amplifier, AilmentType type) { int duration = AilmentScaling.scaleDuration(requestedTicks, source); - target.removeEffect(effect); - boolean applied = target.addEffect(instance(effect, duration, amplifier, true)); + boolean applied = addSourcedEffect(target, source, effect, duration, amplifier, true); if (applied) { EffectSourceUtil.setSource(target, key, source); EffectSourceUtil.setPotency(target, key, AilmentScaling.getPotencyMultiplier(source, type)); @@ -329,6 +322,32 @@ public final class AilmentApi { private static MobEffectInstance instance(MobEffect effect, int duration, int amplifier, boolean icon) { return new MobEffectInstance(effect, duration, amplifier, false, false, icon); } + private static boolean addSourcedEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, + int duration, int amplifier, boolean icon) { + boolean applied = target.addEffect(instance(effect, duration, amplifier, icon), source); + if (applied && AscendCompat.isAscendLoaded()) { + // Ascend observes the standard Forge source and scales duration during addEffect. Ailments already + // applied its source curve, so restore the requested duration to prevent double scaling. + MobEffectInstance active = target.getEffect(effect); + if (active != null && active.getAmplifier() == amplifier) active.mapDuration(ignored -> duration); + } + return applied; + } + private static boolean addStackingEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, + int duration, int amplifier, boolean icon) { + MobEffectInstance previous = target.getEffect(effect); + if (previous == null || previous.getAmplifier() >= amplifier) + return addSourcedEffect(target, source, effect, duration, amplifier, icon); + + // Vanilla preserves a lower-amplifier effect as a hidden fallback when upgrading. These ailments refresh + // to one authoritative stack state, so suppress that fallback without emitting a remove/add lifecycle. + int previousDuration = previous.getDuration(); + previous.mapDuration(ignored -> 0); + boolean applied = addSourcedEffect(target, source, effect, duration, amplifier, icon); + if (!applied && target.getEffect(effect) == previous) + previous.mapDuration(ignored -> previousDuration); + return applied; + } private static boolean serverValid(LivingEntity target) { return target != null && !target.level().isClientSide && target.isAlive() && (!(target instanceof Player player) || !player.isSpectator()); @@ -347,7 +366,10 @@ public final class AilmentApi { return (int) Math.min(Integer.MAX_VALUE, (long) a + b); } private static boolean pvpProcReady(LivingEntity target, @Nullable LivingEntity source, AilmentType type) { - if (!(source instanceof Player) || !(target instanceof Player) || source == target) return true; + if (source != null && source.level() != target.level()) return false; + if (!(source instanceof Player sourcePlayer) || !(target instanceof Player targetPlayer) || source == target) + return true; + if (!sourcePlayer.canHarmPlayer(targetPlayer)) return false; String key = pvpCooldownKey(type); long now = source.level().getGameTime(); return !source.getPersistentData().contains(key) diff --git a/src/main/java/net/fretux/ailments/compat/AscendCompat.java b/src/main/java/net/fretux/ailments/compat/AscendCompat.java index 5b17124..42cc0c9 100644 --- a/src/main/java/net/fretux/ailments/compat/AscendCompat.java +++ b/src/main/java/net/fretux/ailments/compat/AscendCompat.java @@ -25,7 +25,7 @@ public final class AscendCompat { try { resolve(); return statsCapability != null && getAttributeLevel != null; - } catch (ReflectiveOperationException | LinkageError ignored) { + } catch (ReflectiveOperationException | LinkageError | RuntimeException ignored) { return false; } } @@ -39,20 +39,27 @@ public final class AscendCompat { Optional stats = lazy.resolve(); if (stats.isEmpty()) return 0; return Math.max(0, ((Number) getAttributeLevel.invoke(stats.get(), "arcane")).intValue()); - } catch (ReflectiveOperationException | LinkageError ignored) { + } catch (ReflectiveOperationException | LinkageError | RuntimeException ignored) { return 0; } } private static synchronized void resolve() throws ReflectiveOperationException { if (resolved) return; - resolved = true; Class provider = Class.forName(PROVIDER, false, AscendCompat.class.getClassLoader()); Field capabilityField = provider.getField("PLAYER_STATS"); - statsCapability = (Capability) capabilityField.get(null); + Capability resolvedCapability = (Capability) capabilityField.get(null); Class statsClass = Class.forName("net.fretux.ascend.player.PlayerStats", false, AscendCompat.class.getClassLoader()); - getAttributeLevel = statsClass.getMethod("getAttributeLevel", String.class); + Method resolvedGetter = statsClass.getMethod("getAttributeLevel", String.class); + Class returnType = resolvedGetter.getReturnType(); + boolean numericPrimitive = returnType == byte.class || returnType == short.class || returnType == int.class + || returnType == long.class || returnType == float.class || returnType == double.class; + if (!Number.class.isAssignableFrom(returnType) && !numericPrimitive) + throw new NoSuchMethodException("getAttributeLevel(String) does not return a number"); + statsCapability = resolvedCapability; + getAttributeLevel = resolvedGetter; + resolved = true; } private AscendCompat() {} } diff --git a/src/main/java/net/fretux/ailments/damage/ModDamageSources.java b/src/main/java/net/fretux/ailments/damage/ModDamageSources.java index e9f7f35..1652cba 100644 --- a/src/main/java/net/fretux/ailments/damage/ModDamageSources.java +++ b/src/main/java/net/fretux/ailments/damage/ModDamageSources.java @@ -6,15 +6,23 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageType; +import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; +import javax.annotation.Nullable; + public final class ModDamageSources { public static final ResourceKey BLEED = ResourceKey.create(Registries.DAMAGE_TYPE, ResourceLocation.fromNamespaceAndPath(AscendAilments.MOD_ID, "bleed")); public static DamageSource bleed(Level level) { - return new DamageSource(level.registryAccess().registryOrThrow(Registries.DAMAGE_TYPE) - .getHolderOrThrow(BLEED)); + return bleed(level, null); + } + + public static DamageSource bleed(Level level, @Nullable Entity source) { + var type = level.registryAccess().registryOrThrow(Registries.DAMAGE_TYPE).getHolderOrThrow(BLEED); + // Periodic Bleed has an owning attacker for attribution, but no direct entity (it is not another melee hit). + return source == null ? new DamageSource(type) : new DamageSource(type, null, source); } private ModDamageSources() {} } diff --git a/src/main/java/net/fretux/ailments/effect/BleedEffect.java b/src/main/java/net/fretux/ailments/effect/BleedEffect.java index fe1d329..7540daa 100644 --- a/src/main/java/net/fretux/ailments/effect/BleedEffect.java +++ b/src/main/java/net/fretux/ailments/effect/BleedEffect.java @@ -24,7 +24,8 @@ public final class BleedEffect extends MobEffect { : amplifier == 1 ? AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_1) : AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_2); float potency = EffectSourceUtil.getPotency(entity, EffectSourceUtil.BLEED); - boolean damaged = entity.hurt(ModDamageSources.bleed(entity.level()), (float) (base * potency)); + LivingEntity source = EffectSourceUtil.getSourceInLevel(entity, EffectSourceUtil.BLEED); + boolean damaged = entity.hurt(ModDamageSources.bleed(entity.level(), source), (float) (base * potency)); if (damaged) HemorrhageTracker.recordBleedDamage(entity, amplifier, potency); } } diff --git a/src/main/java/net/fretux/ailments/event/AilmentEvents.java b/src/main/java/net/fretux/ailments/event/AilmentEvents.java index 85a8489..6ebe147 100644 --- a/src/main/java/net/fretux/ailments/event/AilmentEvents.java +++ b/src/main/java/net/fretux/ailments/event/AilmentEvents.java @@ -3,6 +3,7 @@ package net.fretux.ailments.event; import net.fretux.ailments.AscendAilments; import net.fretux.ailments.api.AilmentApi; import net.fretux.ailments.config.AilmentsConfig; +import net.fretux.ailments.damage.ModDamageSources; import net.fretux.ailments.registry.ModEffects; import net.fretux.ailments.util.ControlEffectHelper; import net.fretux.ailments.util.EffectSourceUtil; @@ -20,10 +21,12 @@ import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.event.entity.living.LivingChangeTargetEvent; import net.minecraftforge.event.entity.living.LivingEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; +import net.minecraftforge.event.entity.living.LivingDamageEvent; import net.minecraftforge.event.entity.living.LivingHealEvent; import net.minecraftforge.event.entity.living.LivingHurtEvent; import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.registries.ForgeRegistries; @@ -54,12 +57,14 @@ public final class AilmentEvents { @SubscribeEvent public static void onHeal(LivingHealEvent event) { - if (!event.isCanceled() && event.getEntity().hasEffect(ModEffects.SOUL_ROT.get())) + if (!event.getEntity().level().isClientSide && !event.isCanceled() + && event.getEntity().hasEffect(ModEffects.SOUL_ROT.get())) event.setAmount((float) (event.getAmount() * AilmentsConfig.value(AilmentsConfig.SOUL_ROT_HEALING))); } @SubscribeEvent public static void onAttack(LivingAttackEvent event) { + if (event.getEntity().level().isClientSide) return; Entity attacker = event.getSource().getEntity(); if (attacker instanceof LivingEntity living && !(living instanceof Player) && living.hasEffect(ModEffects.CHARM.get()) @@ -68,6 +73,7 @@ public final class AilmentEvents { @SubscribeEvent public static void onChangeTarget(LivingChangeTargetEvent event) { + if (event.getEntity().level().isClientSide) return; if (!(event.getEntity() instanceof Mob mob)) return; LivingEntity taunter = mob.hasEffect(ModEffects.TAUNT.get()) && !MentalControlUtil.isMentalControlResistant(mob) @@ -82,11 +88,12 @@ public final class AilmentEvents { @SubscribeEvent public static void onHurt(LivingHurtEvent event) { - if (event.isCanceled()) return; + if (event.getEntity().level().isClientSide || event.isCanceled()) return; LivingEntity victim = event.getEntity(); DamageSource damageSource = event.getSource(); Entity causing = damageSource.getEntity(); - LivingEntity attacker = causing instanceof LivingEntity living ? living : null; + LivingEntity attacker = !damageSource.is(ModDamageSources.BLEED) && causing instanceof LivingEntity living + ? living : null; float amount = event.getAmount(); // 1. Victim vulnerability. @@ -119,14 +126,25 @@ public final class AilmentEvents { boolean directPlayerMelee = attacker instanceof Player && damageSource.getDirectEntity() == attacker; if (alreadyCharmedByAttacker && directPlayerMelee) amount *= (float) AilmentsConfig.value(AilmentsConfig.OVERCHARM_DAMAGE_BONUS); + } + event.setAmount(amount); + } + + /** Applies hit-triggered ailments only after armor, absorption, and earlier cancellation hooks are resolved. */ + @SubscribeEvent(priority = EventPriority.LOWEST) + public static void onDamage(LivingDamageEvent event) { + if (event.getEntity().level().isClientSide || event.isCanceled() || event.getAmount() <= 0) return; + LivingEntity victim = event.getEntity(); + DamageSource damageSource = event.getSource(); + if (damageSource.is(ModDamageSources.BLEED)) return; + Entity causing = damageSource.getEntity(); + if (!(causing instanceof LivingEntity attacker) || attacker == victim) return; + + if (attacker.hasEffect(ModEffects.OVERCHARM.get())) AilmentApi.applyCharm(victim, attacker, AilmentsConfig.value(AilmentsConfig.OVERCHARM_CHARM_DURATION)); - } - - if (attacker != null && attacker != victim && amount > 0 - && damageSource.getDirectEntity() == attacker) + if (damageSource.getDirectEntity() == attacker) applyTaggedWeaponAilments(attacker.getMainHandItem(), victim, attacker); - event.setAmount(amount); } private static void applyTaggedWeaponAilments(ItemStack stack, LivingEntity victim, LivingEntity attacker) { diff --git a/src/main/java/net/fretux/ailments/util/ControlEffectHelper.java b/src/main/java/net/fretux/ailments/util/ControlEffectHelper.java index 16f4205..7726a3f 100644 --- a/src/main/java/net/fretux/ailments/util/ControlEffectHelper.java +++ b/src/main/java/net/fretux/ailments/util/ControlEffectHelper.java @@ -6,17 +6,23 @@ import net.fretux.ailments.registry.ModEffects; import net.minecraft.commands.arguments.EntityAnchorArgument; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.effect.MobEffectInstance; -import net.minecraft.world.effect.MobEffects; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; +import net.minecraft.world.entity.ai.attributes.AttributeInstance; +import net.minecraft.world.entity.ai.attributes.AttributeModifier; +import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.entity.player.Player; import net.minecraft.world.food.FoodData; import net.minecraft.world.phys.Vec3; +import java.util.UUID; + public final class ControlEffectHelper { private static final String FEAR_DISTANCE = "ascend_ailments.fear.last_distance_sqr"; private static final String SOUL_FOOD = "ascend_ailments.soul_rot.last_food"; private static final String SOUL_SATURATION = "ascend_ailments.soul_rot.last_saturation"; + private static final UUID FEAR_MOVEMENT_UUID = UUID.fromString("8158dc2a-8de9-49a2-b086-bd99ade47661"); + private static final UUID FEAR_ATTACK_UUID = UUID.fromString("4468e408-78e8-49f6-a7bc-9ae9028a0f48"); public static void handleLivingTick(LivingEntity entity) { if (entity.level().isClientSide) return; @@ -26,6 +32,7 @@ public final class ControlEffectHelper { else { EffectSourceUtil.clear(entity, EffectSourceUtil.FEAR); entity.getPersistentData().remove(FEAR_DISTANCE); + clearFearPlayerPenalties(entity); } if (entity.hasEffect(ModEffects.TAUNT.get())) tickTaunt(entity); else EffectSourceUtil.clear(entity, EffectSourceUtil.TAUNT); @@ -36,14 +43,20 @@ public final class ControlEffectHelper { } private static void tickFear(LivingEntity entity) { - LivingEntity source = validControlSource(entity, EffectSourceUtil.FEAR); - if (source == null) { - entity.removeEffect(ModEffects.FEAR.get()); - EffectSourceUtil.clear(entity, EffectSourceUtil.FEAR); + if (EffectSourceUtil.getSourceUuid(entity, EffectSourceUtil.FEAR) == null) { + removeControlEffect(entity, ModEffects.FEAR.get(), EffectSourceUtil.FEAR); entity.getPersistentData().remove(FEAR_DISTANCE); return; } + LivingEntity source = validControlSource(entity, EffectSourceUtil.FEAR); + if (source == null) { + if (entity instanceof Mob mob) mob.getNavigation().stop(); + entity.getPersistentData().remove(FEAR_DISTANCE); + clearFearPlayerPenalties(entity); + return; + } if (MentalControlUtil.isMentalControlResistant(entity)) { + clearFearPlayerPenalties(entity); MobEffectInstance winded = entity.getEffect(ModEffects.WINDED.get()); if (winded == null || winded.getDuration() < 10) entity.addEffect(new MobEffectInstance(ModEffects.WINDED.get(), 25, 0, false, false, false)); @@ -86,17 +99,17 @@ public final class ControlEffectHelper { } } data.putDouble(FEAR_DISTANCE, distance); - if (distance <= 36.0) { - player.addEffect(new MobEffectInstance(MobEffects.MOVEMENT_SLOWDOWN, 20, 3, false, false, false)); - player.addEffect(new MobEffectInstance(MobEffects.WEAKNESS, 20, 0, false, false, false)); - } + setFearPlayerPenalties(player, distance <= 36.0); } private static void tickTaunt(LivingEntity entity) { + if (EffectSourceUtil.getSourceUuid(entity, EffectSourceUtil.TAUNT) == null) { + removeControlEffect(entity, ModEffects.TAUNT.get(), EffectSourceUtil.TAUNT); + return; + } LivingEntity source = validControlSource(entity, EffectSourceUtil.TAUNT); if (source == null) { - entity.removeEffect(ModEffects.TAUNT.get()); - EffectSourceUtil.clear(entity, EffectSourceUtil.TAUNT); + if (entity instanceof Mob mob && mob.getTarget() != null) mob.setTarget(null); return; } if (MentalControlUtil.isMentalControlResistant(entity)) return; @@ -106,10 +119,8 @@ public final class ControlEffectHelper { } private static void validateCharm(LivingEntity entity) { - if (validControlSource(entity, EffectSourceUtil.CHARM) == null) { - entity.removeEffect(ModEffects.CHARM.get()); - EffectSourceUtil.clear(entity, EffectSourceUtil.CHARM); - } + if (EffectSourceUtil.getSourceUuid(entity, EffectSourceUtil.CHARM) == null) + removeControlEffect(entity, ModEffects.CHARM.get(), EffectSourceUtil.CHARM); } public static boolean isOwnCharmSource(LivingEntity charmed, LivingEntity possibleSource) { @@ -122,8 +133,37 @@ public final class ControlEffectHelper { } private static LivingEntity validControlSource(LivingEntity target, String key) { - LivingEntity source = EffectSourceUtil.getSource(target, key); - return source != null && source.isAlive() && source.level() == target.level() ? source : null; + LivingEntity source = EffectSourceUtil.getSourceInLevel(target, key); + return source != null && source.isAlive() ? source : null; + } + + private static void setFearPlayerPenalties(Player player, boolean active) { + setTransientModifier(player.getAttribute(Attributes.MOVEMENT_SPEED), FEAR_MOVEMENT_UUID, + "Ascend Ailments fear movement", -0.60, AttributeModifier.Operation.MULTIPLY_TOTAL, active); + setTransientModifier(player.getAttribute(Attributes.ATTACK_DAMAGE), FEAR_ATTACK_UUID, + "Ascend Ailments fear weakness", -4.0, AttributeModifier.Operation.ADDITION, active); + } + + private static void setTransientModifier(AttributeInstance attribute, UUID id, String name, double amount, + AttributeModifier.Operation operation, boolean active) { + if (attribute == null) return; + AttributeModifier existing = attribute.getModifier(id); + if (!active) { + if (existing != null) attribute.removeModifier(id); + } else if (existing == null) { + attribute.addTransientModifier(new AttributeModifier(id, name, amount, operation)); + } + } + + private static void clearFearPlayerPenalties(LivingEntity entity) { + if (entity instanceof Player player) setFearPlayerPenalties(player, false); + } + + private static void removeControlEffect(LivingEntity entity, net.minecraft.world.effect.MobEffect effect, + String key) { + entity.removeEffect(effect); + EffectSourceUtil.clear(entity, key); + if (key.equals(EffectSourceUtil.FEAR)) clearFearPlayerPenalties(entity); } private static void handleSoulRotFood(LivingEntity entity) { diff --git a/src/main/java/net/fretux/ailments/util/EffectSourceUtil.java b/src/main/java/net/fretux/ailments/util/EffectSourceUtil.java index 362943e..0d2801f 100644 --- a/src/main/java/net/fretux/ailments/util/EffectSourceUtil.java +++ b/src/main/java/net/fretux/ailments/util/EffectSourceUtil.java @@ -34,13 +34,26 @@ public final class EffectSourceUtil { UUID id = getSourceUuid(target, ailment); if (id == null || !(target.level() instanceof ServerLevel level)) return null; MinecraftServer server = level.getServer(); + Entity player = server.getPlayerList().getPlayer(id); + if (player instanceof LivingEntity living) return living; + Entity local = level.getEntity(id); + if (local instanceof LivingEntity living) return living; for (ServerLevel candidate : server.getAllLevels()) { + if (candidate == level) continue; Entity entity = candidate.getEntity(id); if (entity instanceof LivingEntity living) return living; } return null; } + /** Resolves a loaded source only in the target's current level without scanning every server dimension. */ + @Nullable public static LivingEntity getSourceInLevel(LivingEntity target, String ailment) { + UUID id = getSourceUuid(target, ailment); + if (id == null || !(target.level() instanceof ServerLevel level)) return null; + Entity entity = level.getEntity(id); + return entity instanceof LivingEntity living ? living : null; + } + public static void setPotency(LivingEntity target, String ailment, double potency) { target.getPersistentData().putFloat(potencyKey(ailment), (float) Math.max(0, potency)); } diff --git a/src/main/java/net/fretux/ailments/util/HemorrhageTracker.java b/src/main/java/net/fretux/ailments/util/HemorrhageTracker.java index 3f8e76b..ea210fe 100644 --- a/src/main/java/net/fretux/ailments/util/HemorrhageTracker.java +++ b/src/main/java/net/fretux/ailments/util/HemorrhageTracker.java @@ -34,7 +34,8 @@ public final class HemorrhageTracker { if (target.isAlive()) { float damage = (float) (target.getMaxHealth() * AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_MAX_HEALTH_DAMAGE)); - if (damage > 0) target.hurt(ModDamageSources.bleed(target.level()), damage); + LivingEntity source = EffectSourceUtil.getSourceInLevel(target, EffectSourceUtil.BLEED); + if (damage > 0) target.hurt(ModDamageSources.bleed(target.level(), source), damage); } return true; }