fix various issues throughout the entire project

This commit is contained in:
2026-08-26 19:38:15 +02:00
parent 03deff254f
commit 5650e9979f
10 changed files with 164 additions and 53 deletions
+1 -1
View File
@@ -92,7 +92,7 @@ Item tags:
Direct melee hits with items in these tags apply their corresponding ailment. Soul Rot, Bleed, and Fracture use 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 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, 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). tracked per attacking player and ailment; their configured minimum is 60 ticks (3 seconds).
## Admin commands ## Admin commands
@@ -25,8 +25,9 @@ public final class AscendAilments {
ModEffects.register(modBus); ModEffects.register(modBus);
AilmentNetwork.register(); AilmentNetwork.register();
modBus.addListener(this::commonSetup); modBus.addListener(this::commonSetup);
context.registerConfig(ModConfig.Type.COMMON, AilmentsConfig.SPEC, // Gameplay settings belong to the server and are synchronized to multiplayer clients by Forge.
"ascend-ailments-common.toml"); context.registerConfig(ModConfig.Type.SERVER, AilmentsConfig.SPEC,
"ascend-ailments-server.toml");
MinecraftForge.EVENT_BUS.addListener(this::registerCommands); MinecraftForge.EVENT_BUS.addListener(this::registerCommands);
} }
@@ -1,6 +1,7 @@
package net.fretux.ailments.api; package net.fretux.ailments.api;
import net.fretux.ailments.config.AilmentsConfig; import net.fretux.ailments.config.AilmentsConfig;
import net.fretux.ailments.compat.AscendCompat;
import net.fretux.ailments.registry.ModEffects; import net.fretux.ailments.registry.ModEffects;
import net.fretux.ailments.util.EffectSourceUtil; import net.fretux.ailments.util.EffectSourceUtil;
import net.fretux.ailments.util.MentalControlUtil; import net.fretux.ailments.util.MentalControlUtil;
@@ -117,8 +118,7 @@ public final class AilmentApi {
int base = AilmentsConfig.value(AilmentsConfig.SOUL_ROT_DURATION); int base = AilmentsConfig.value(AilmentsConfig.SOUL_ROT_DURATION);
if (extendWithStacks) base = safeAdd(base, amplifier * 20); if (extendWithStacks) base = safeAdd(base, amplifier * 20);
int duration = AilmentScaling.scaleDuration(base, source); int duration = AilmentScaling.scaleDuration(base, source);
target.removeEffect(effect); boolean applied = addStackingEffect(target, source, effect, duration, amplifier, true);
boolean applied = target.addEffect(instance(effect, duration, amplifier, true));
if (applied) { if (applied) {
EffectSourceUtil.setSource(target, EffectSourceUtil.SOUL_ROT, source); EffectSourceUtil.setSource(target, EffectSourceUtil.SOUL_ROT, source);
EffectSourceUtil.setPotency(target, EffectSourceUtil.SOUL_ROT, EffectSourceUtil.setPotency(target, EffectSourceUtil.SOUL_ROT,
@@ -148,8 +148,7 @@ public final class AilmentApi {
: amplifier == 1 ? AilmentsConfig.value(AilmentsConfig.BLEED_DURATION_1) : amplifier == 1 ? AilmentsConfig.value(AilmentsConfig.BLEED_DURATION_1)
: AilmentsConfig.value(AilmentsConfig.BLEED_DURATION_2); : AilmentsConfig.value(AilmentsConfig.BLEED_DURATION_2);
int duration = AilmentScaling.scaleDuration(base, source); int duration = AilmentScaling.scaleDuration(base, source);
target.removeEffect(effect); boolean applied = addStackingEffect(target, source, effect, duration, amplifier, true);
boolean applied = target.addEffect(instance(effect, duration, amplifier, true));
if (applied) { if (applied) {
EffectSourceUtil.setSource(target, EffectSourceUtil.BLEED, source); EffectSourceUtil.setSource(target, EffectSourceUtil.BLEED, source);
EffectSourceUtil.setPotency(target, EffectSourceUtil.BLEED, 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 stackedAmplifier = current == null ? 0 : Math.min(2, current.getAmplifier() + 1);
int appliedAmplifier = Math.min(2, Math.max(stackedAmplifier, amplifier)); int appliedAmplifier = Math.min(2, Math.max(stackedAmplifier, amplifier));
int duration = AilmentScaling.scaleDuration(requestedTicks, source); int duration = AilmentScaling.scaleDuration(requestedTicks, source);
target.removeEffect(effect); boolean applied = addStackingEffect(target, source, effect, duration, appliedAmplifier, true);
boolean applied = target.addEffect(instance(effect, duration, appliedAmplifier, true));
if (applied) EffectSourceUtil.setSource(target, EffectSourceUtil.FRACTURE, source); if (applied) EffectSourceUtil.setSource(target, EffectSourceUtil.FRACTURE, source);
return finishApplication(applied, target, source, AilmentType.FRACTURE); 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) { public static boolean applyOvercharm(LivingEntity target, @Nullable LivingEntity source, int requestedTicks) {
if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.OVERCHARM)) return false; if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.OVERCHARM)) return false;
int duration = AilmentScaling.scaleDuration(requestedTicks, source); 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) { 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); EffectSourceUtil.setSource(target, EffectSourceUtil.OVERCHARM, source);
} }
return finishApplication(result, target, source, AilmentType.OVERCHARM); 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) { public static boolean applyWinded(LivingEntity target, @Nullable LivingEntity source, int requestedTicks) {
if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.WINDED)) return false; if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.WINDED)) return false;
int duration = AilmentScaling.scaleDuration(requestedTicks, source); 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); target, source, AilmentType.WINDED);
} }
@@ -311,15 +305,14 @@ public final class AilmentApi {
private static boolean applySourced(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, private static boolean applySourced(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
String key, int duration, int amplifier) { 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); if (applied) EffectSourceUtil.setSource(target, key, source);
return applied; return applied;
} }
private static boolean applyDot(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, private static boolean applyDot(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
String key, int requestedTicks, int amplifier, AilmentType type) { String key, int requestedTicks, int amplifier, AilmentType type) {
int duration = AilmentScaling.scaleDuration(requestedTicks, source); int duration = AilmentScaling.scaleDuration(requestedTicks, source);
target.removeEffect(effect); boolean applied = addSourcedEffect(target, source, effect, duration, amplifier, true);
boolean applied = target.addEffect(instance(effect, duration, amplifier, true));
if (applied) { if (applied) {
EffectSourceUtil.setSource(target, key, source); EffectSourceUtil.setSource(target, key, source);
EffectSourceUtil.setPotency(target, key, AilmentScaling.getPotencyMultiplier(source, type)); 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) { private static MobEffectInstance instance(MobEffect effect, int duration, int amplifier, boolean icon) {
return new MobEffectInstance(effect, duration, amplifier, false, false, 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) { private static boolean serverValid(LivingEntity target) {
return target != null && !target.level().isClientSide && target.isAlive() return target != null && !target.level().isClientSide && target.isAlive()
&& (!(target instanceof Player player) || !player.isSpectator()); && (!(target instanceof Player player) || !player.isSpectator());
@@ -347,7 +366,10 @@ public final class AilmentApi {
return (int) Math.min(Integer.MAX_VALUE, (long) a + b); return (int) Math.min(Integer.MAX_VALUE, (long) a + b);
} }
private static boolean pvpProcReady(LivingEntity target, @Nullable LivingEntity source, AilmentType type) { 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); String key = pvpCooldownKey(type);
long now = source.level().getGameTime(); long now = source.level().getGameTime();
return !source.getPersistentData().contains(key) return !source.getPersistentData().contains(key)
@@ -25,7 +25,7 @@ public final class AscendCompat {
try { try {
resolve(); resolve();
return statsCapability != null && getAttributeLevel != null; return statsCapability != null && getAttributeLevel != null;
} catch (ReflectiveOperationException | LinkageError ignored) { } catch (ReflectiveOperationException | LinkageError | RuntimeException ignored) {
return false; return false;
} }
} }
@@ -39,20 +39,27 @@ public final class AscendCompat {
Optional<?> stats = lazy.resolve(); Optional<?> stats = lazy.resolve();
if (stats.isEmpty()) return 0; if (stats.isEmpty()) return 0;
return Math.max(0, ((Number) getAttributeLevel.invoke(stats.get(), "arcane")).intValue()); return Math.max(0, ((Number) getAttributeLevel.invoke(stats.get(), "arcane")).intValue());
} catch (ReflectiveOperationException | LinkageError ignored) { } catch (ReflectiveOperationException | LinkageError | RuntimeException ignored) {
return 0; return 0;
} }
} }
private static synchronized void resolve() throws ReflectiveOperationException { private static synchronized void resolve() throws ReflectiveOperationException {
if (resolved) return; if (resolved) return;
resolved = true;
Class<?> provider = Class.forName(PROVIDER, false, AscendCompat.class.getClassLoader()); Class<?> provider = Class.forName(PROVIDER, false, AscendCompat.class.getClassLoader());
Field capabilityField = provider.getField("PLAYER_STATS"); 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, Class<?> statsClass = Class.forName("net.fretux.ascend.player.PlayerStats", false,
AscendCompat.class.getClassLoader()); 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() {} private AscendCompat() {}
} }
@@ -6,15 +6,23 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.damagesource.DamageSource;
import net.minecraft.world.damagesource.DamageType; import net.minecraft.world.damagesource.DamageType;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.level.Level; import net.minecraft.world.level.Level;
import javax.annotation.Nullable;
public final class ModDamageSources { public final class ModDamageSources {
public static final ResourceKey<DamageType> BLEED = ResourceKey.create(Registries.DAMAGE_TYPE, public static final ResourceKey<DamageType> BLEED = ResourceKey.create(Registries.DAMAGE_TYPE,
ResourceLocation.fromNamespaceAndPath(AscendAilments.MOD_ID, "bleed")); ResourceLocation.fromNamespaceAndPath(AscendAilments.MOD_ID, "bleed"));
public static DamageSource bleed(Level level) { public static DamageSource bleed(Level level) {
return new DamageSource(level.registryAccess().registryOrThrow(Registries.DAMAGE_TYPE) return bleed(level, null);
.getHolderOrThrow(BLEED)); }
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() {} private ModDamageSources() {}
} }
@@ -24,7 +24,8 @@ public final class BleedEffect extends MobEffect {
: amplifier == 1 ? AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_1) : amplifier == 1 ? AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_1)
: AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_2); : AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_2);
float potency = EffectSourceUtil.getPotency(entity, EffectSourceUtil.BLEED); 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); if (damaged) HemorrhageTracker.recordBleedDamage(entity, amplifier, potency);
} }
} }
@@ -3,6 +3,7 @@ package net.fretux.ailments.event;
import net.fretux.ailments.AscendAilments; import net.fretux.ailments.AscendAilments;
import net.fretux.ailments.api.AilmentApi; import net.fretux.ailments.api.AilmentApi;
import net.fretux.ailments.config.AilmentsConfig; import net.fretux.ailments.config.AilmentsConfig;
import net.fretux.ailments.damage.ModDamageSources;
import net.fretux.ailments.registry.ModEffects; import net.fretux.ailments.registry.ModEffects;
import net.fretux.ailments.util.ControlEffectHelper; import net.fretux.ailments.util.ControlEffectHelper;
import net.fretux.ailments.util.EffectSourceUtil; 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.LivingChangeTargetEvent;
import net.minecraftforge.event.entity.living.LivingEvent; import net.minecraftforge.event.entity.living.LivingEvent;
import net.minecraftforge.event.entity.living.LivingDeathEvent; 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.LivingHealEvent;
import net.minecraftforge.event.entity.living.LivingHurtEvent; import net.minecraftforge.event.entity.living.LivingHurtEvent;
import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.entity.player.PlayerEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import net.minecraftforge.eventbus.api.EventPriority;
import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.common.Mod;
import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistries;
@@ -54,12 +57,14 @@ public final class AilmentEvents {
@SubscribeEvent @SubscribeEvent
public static void onHeal(LivingHealEvent event) { 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))); event.setAmount((float) (event.getAmount() * AilmentsConfig.value(AilmentsConfig.SOUL_ROT_HEALING)));
} }
@SubscribeEvent @SubscribeEvent
public static void onAttack(LivingAttackEvent event) { public static void onAttack(LivingAttackEvent event) {
if (event.getEntity().level().isClientSide) return;
Entity attacker = event.getSource().getEntity(); Entity attacker = event.getSource().getEntity();
if (attacker instanceof LivingEntity living && !(living instanceof Player) if (attacker instanceof LivingEntity living && !(living instanceof Player)
&& living.hasEffect(ModEffects.CHARM.get()) && living.hasEffect(ModEffects.CHARM.get())
@@ -68,6 +73,7 @@ public final class AilmentEvents {
@SubscribeEvent @SubscribeEvent
public static void onChangeTarget(LivingChangeTargetEvent event) { public static void onChangeTarget(LivingChangeTargetEvent event) {
if (event.getEntity().level().isClientSide) return;
if (!(event.getEntity() instanceof Mob mob)) return; if (!(event.getEntity() instanceof Mob mob)) return;
LivingEntity taunter = mob.hasEffect(ModEffects.TAUNT.get()) LivingEntity taunter = mob.hasEffect(ModEffects.TAUNT.get())
&& !MentalControlUtil.isMentalControlResistant(mob) && !MentalControlUtil.isMentalControlResistant(mob)
@@ -82,11 +88,12 @@ public final class AilmentEvents {
@SubscribeEvent @SubscribeEvent
public static void onHurt(LivingHurtEvent event) { public static void onHurt(LivingHurtEvent event) {
if (event.isCanceled()) return; if (event.getEntity().level().isClientSide || event.isCanceled()) return;
LivingEntity victim = event.getEntity(); LivingEntity victim = event.getEntity();
DamageSource damageSource = event.getSource(); DamageSource damageSource = event.getSource();
Entity causing = damageSource.getEntity(); 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(); float amount = event.getAmount();
// 1. Victim vulnerability. // 1. Victim vulnerability.
@@ -119,14 +126,25 @@ public final class AilmentEvents {
boolean directPlayerMelee = attacker instanceof Player && damageSource.getDirectEntity() == attacker; boolean directPlayerMelee = attacker instanceof Player && damageSource.getDirectEntity() == attacker;
if (alreadyCharmedByAttacker && directPlayerMelee) if (alreadyCharmedByAttacker && directPlayerMelee)
amount *= (float) AilmentsConfig.value(AilmentsConfig.OVERCHARM_DAMAGE_BONUS); amount *= (float) AilmentsConfig.value(AilmentsConfig.OVERCHARM_DAMAGE_BONUS);
AilmentApi.applyCharm(victim, attacker, }
AilmentsConfig.value(AilmentsConfig.OVERCHARM_CHARM_DURATION)); event.setAmount(amount);
} }
if (attacker != null && attacker != victim && amount > 0 /** Applies hit-triggered ailments only after armor, absorption, and earlier cancellation hooks are resolved. */
&& damageSource.getDirectEntity() == attacker) @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 (damageSource.getDirectEntity() == attacker)
applyTaggedWeaponAilments(attacker.getMainHandItem(), victim, attacker); applyTaggedWeaponAilments(attacker.getMainHandItem(), victim, attacker);
event.setAmount(amount);
} }
private static void applyTaggedWeaponAilments(ItemStack stack, LivingEntity victim, LivingEntity attacker) { private static void applyTaggedWeaponAilments(ItemStack stack, LivingEntity victim, LivingEntity attacker) {
@@ -6,17 +6,23 @@ import net.fretux.ailments.registry.ModEffects;
import net.minecraft.commands.arguments.EntityAnchorArgument; import net.minecraft.commands.arguments.EntityAnchorArgument;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.Mob; 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.entity.player.Player;
import net.minecraft.world.food.FoodData; import net.minecraft.world.food.FoodData;
import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.Vec3;
import java.util.UUID;
public final class ControlEffectHelper { public final class ControlEffectHelper {
private static final String FEAR_DISTANCE = "ascend_ailments.fear.last_distance_sqr"; 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_FOOD = "ascend_ailments.soul_rot.last_food";
private static final String SOUL_SATURATION = "ascend_ailments.soul_rot.last_saturation"; 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) { public static void handleLivingTick(LivingEntity entity) {
if (entity.level().isClientSide) return; if (entity.level().isClientSide) return;
@@ -26,6 +32,7 @@ public final class ControlEffectHelper {
else { else {
EffectSourceUtil.clear(entity, EffectSourceUtil.FEAR); EffectSourceUtil.clear(entity, EffectSourceUtil.FEAR);
entity.getPersistentData().remove(FEAR_DISTANCE); entity.getPersistentData().remove(FEAR_DISTANCE);
clearFearPlayerPenalties(entity);
} }
if (entity.hasEffect(ModEffects.TAUNT.get())) tickTaunt(entity); if (entity.hasEffect(ModEffects.TAUNT.get())) tickTaunt(entity);
else EffectSourceUtil.clear(entity, EffectSourceUtil.TAUNT); else EffectSourceUtil.clear(entity, EffectSourceUtil.TAUNT);
@@ -36,14 +43,20 @@ public final class ControlEffectHelper {
} }
private static void tickFear(LivingEntity entity) { private static void tickFear(LivingEntity entity) {
LivingEntity source = validControlSource(entity, EffectSourceUtil.FEAR); if (EffectSourceUtil.getSourceUuid(entity, EffectSourceUtil.FEAR) == null) {
if (source == null) { removeControlEffect(entity, ModEffects.FEAR.get(), EffectSourceUtil.FEAR);
entity.removeEffect(ModEffects.FEAR.get());
EffectSourceUtil.clear(entity, EffectSourceUtil.FEAR);
entity.getPersistentData().remove(FEAR_DISTANCE); entity.getPersistentData().remove(FEAR_DISTANCE);
return; 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)) { if (MentalControlUtil.isMentalControlResistant(entity)) {
clearFearPlayerPenalties(entity);
MobEffectInstance winded = entity.getEffect(ModEffects.WINDED.get()); MobEffectInstance winded = entity.getEffect(ModEffects.WINDED.get());
if (winded == null || winded.getDuration() < 10) if (winded == null || winded.getDuration() < 10)
entity.addEffect(new MobEffectInstance(ModEffects.WINDED.get(), 25, 0, false, false, false)); 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); data.putDouble(FEAR_DISTANCE, distance);
if (distance <= 36.0) { setFearPlayerPenalties(player, 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));
}
} }
private static void tickTaunt(LivingEntity entity) { 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); LivingEntity source = validControlSource(entity, EffectSourceUtil.TAUNT);
if (source == null) { if (source == null) {
entity.removeEffect(ModEffects.TAUNT.get()); if (entity instanceof Mob mob && mob.getTarget() != null) mob.setTarget(null);
EffectSourceUtil.clear(entity, EffectSourceUtil.TAUNT);
return; return;
} }
if (MentalControlUtil.isMentalControlResistant(entity)) return; if (MentalControlUtil.isMentalControlResistant(entity)) return;
@@ -106,10 +119,8 @@ public final class ControlEffectHelper {
} }
private static void validateCharm(LivingEntity entity) { private static void validateCharm(LivingEntity entity) {
if (validControlSource(entity, EffectSourceUtil.CHARM) == null) { if (EffectSourceUtil.getSourceUuid(entity, EffectSourceUtil.CHARM) == null)
entity.removeEffect(ModEffects.CHARM.get()); removeControlEffect(entity, ModEffects.CHARM.get(), EffectSourceUtil.CHARM);
EffectSourceUtil.clear(entity, EffectSourceUtil.CHARM);
}
} }
public static boolean isOwnCharmSource(LivingEntity charmed, LivingEntity possibleSource) { public static boolean isOwnCharmSource(LivingEntity charmed, LivingEntity possibleSource) {
@@ -122,8 +133,37 @@ public final class ControlEffectHelper {
} }
private static LivingEntity validControlSource(LivingEntity target, String key) { private static LivingEntity validControlSource(LivingEntity target, String key) {
LivingEntity source = EffectSourceUtil.getSource(target, key); LivingEntity source = EffectSourceUtil.getSourceInLevel(target, key);
return source != null && source.isAlive() && source.level() == target.level() ? source : null; 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) { private static void handleSoulRotFood(LivingEntity entity) {
@@ -34,13 +34,26 @@ public final class EffectSourceUtil {
UUID id = getSourceUuid(target, ailment); UUID id = getSourceUuid(target, ailment);
if (id == null || !(target.level() instanceof ServerLevel level)) return null; if (id == null || !(target.level() instanceof ServerLevel level)) return null;
MinecraftServer server = level.getServer(); 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()) { for (ServerLevel candidate : server.getAllLevels()) {
if (candidate == level) continue;
Entity entity = candidate.getEntity(id); Entity entity = candidate.getEntity(id);
if (entity instanceof LivingEntity living) return living; if (entity instanceof LivingEntity living) return living;
} }
return null; 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) { public static void setPotency(LivingEntity target, String ailment, double potency) {
target.getPersistentData().putFloat(potencyKey(ailment), (float) Math.max(0, potency)); target.getPersistentData().putFloat(potencyKey(ailment), (float) Math.max(0, potency));
} }
@@ -34,7 +34,8 @@ public final class HemorrhageTracker {
if (target.isAlive()) { if (target.isAlive()) {
float damage = (float) (target.getMaxHealth() float damage = (float) (target.getMaxHealth()
* AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_MAX_HEALTH_DAMAGE)); * 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; return true;
} }