adjust API

This commit is contained in:
2026-08-27 09:35:42 +02:00
parent 5650e9979f
commit f01e78c1f1
8 changed files with 236 additions and 20 deletions
+29 -1
View File
@@ -32,6 +32,29 @@ int appliedCount = AilmentApi.applyEffects(target, source,
Bleed/Soul Rot stacking and `timed` for an explicit duration and amplifier. `AilmentApi.getEffect(type)` exposes the Bleed/Soul Rot stacking and `timed` for an explicit duration and amplifier. `AilmentApi.getEffect(type)` exposes the
registered `MobEffect` when an integration needs to inspect an active effect without depending on registry internals. registered `MobEffect` when an integration needs to inspect an active effect without depending on registry internals.
Other mods can use the same source-safe machinery for their own registered effects:
```java
MobEffect frost = MyEffects.FROST.get();
// Raw duration, vanilla merging, real Forge source, and persistent latest-source tracking.
AilmentApi.applyEffect(target, source, frost, 100, 0);
// Add one stack per call, cap at 4 stacks, and refresh to 100 ticks.
AilmentApi.applyStackingEffect(target, source, frost, 100, 4);
// Add one stack, extend remaining duration, and Arcane-scale each added duration chunk once.
AilmentApi.applyStackingEffect(target, source, frost, 100, 4, true, true);
LivingEntity loadedSource = AilmentApi.getEffectSource(target, frost);
UUID sourceId = AilmentApi.getEffectSourceUuid(target, frost);
```
`applyScaledEffect` provides the non-stacking Arcane-scaled variant. Generic applications are server-only, reject
invalid or cross-dimension sources, and honor team/friendly-fire rules for harmful player-on-player effects. Source
metadata is keyed by the external effect's registry ID and is cleared lazily when queried after the effect expires;
`clearEffectSource` can clear it explicitly without removing the effect.
Fracture stacks up to three times. Each stack removes 2 armor and 1 armor toughness; reapplication adds a stack and Fracture stacks up to three times. Each stack removes 2 armor and 1 armor toughness; reapplication adds a stack and
refreshes its duration. Arcane scales that refreshed duration but never changes the fixed attribute penalties. refreshes its duration. Arcane scales that refreshed duration but never changes the fixed attribute penalties.
@@ -73,10 +96,15 @@ snapshotted on application so source unloading does not alter an active effect.
Entity type tags: Entity type tags:
- `ascend_ailments:bleed_immune` - `ascend_ailments:bleed_immune`
- `ascend_ailments:soulless` (immune to Soul Rot)
- `ascend_ailments:sturdy` (immune to Fracture)
- `ascend_ailments:mental_control_resistant` - `ascend_ailments:mental_control_resistant`
- `ascend_ailments:mental_control_immune` - `ascend_ailments:mental_control_immune`
Players remain Bleed-compatible and non-player undead are automatically rejected independently of tags. Vanilla undead are included in `soulless`, and any vanilla or modded entity classified with `MobType.UNDEAD` is
also rejected independently of the tag. `sturdy` includes iron golems by default. Non-player undead remain naturally
Bleed-immune; `bleed_immune` takes precedence even for players and also prevents and clears Hemorrhage buildup.
Integrations can query `AilmentApi.canSoulRot`, `canFracture`, and `canBleed` before presenting or applying abilities.
Item tags: Item tags:
@@ -8,14 +8,17 @@ import net.fretux.ailments.util.MentalControlUtil;
import net.fretux.ailments.util.ModEntityTypeTags; import net.fretux.ailments.util.ModEntityTypeTags;
import net.fretux.ailments.util.HemorrhageTracker; import net.fretux.ailments.util.HemorrhageTracker;
import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectCategory;
import net.minecraft.world.effect.MobEffectInstance; import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.LivingEntity;
import net.minecraft.world.entity.MobType; import net.minecraft.world.entity.MobType;
import net.minecraft.world.entity.monster.EnderMan; import net.minecraft.world.entity.monster.EnderMan;
import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.player.Player;
import net.minecraftforge.registries.ForgeRegistries;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.UUID;
/** Stable, source-aware entry point for applying and querying Ascend: Ailments effects. */ /** Stable, source-aware entry point for applying and querying Ascend: Ailments effects. */
public final class AilmentApi { public final class AilmentApi {
@@ -85,6 +88,97 @@ public final class AilmentApi {
return applied; return applied;
} }
/**
* Applies an effect owned by another mod with standard Forge source propagation and persistent source tracking.
* The requested duration is used exactly and is not Arcane-scaled. Vanilla effect-merging rules apply.
*/
public static boolean applyEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int requestedTicks, int amplifier) {
return applyExternalEffect(target, source, effect, requestedTicks, amplifier, false);
}
/** Applies an amplifier-zero external effect with source propagation and persistent source tracking. */
public static boolean applyEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int requestedTicks) {
return applyEffect(target, source, effect, requestedTicks, 0);
}
/**
* Applies an external effect and scales its requested duration once using the optional Ascend Arcane curve.
* Use this overload only when the owning mod wants Ailments to be the duration-scaling authority.
*/
public static boolean applyScaledEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int requestedTicks, int amplifier) {
return applyExternalEffect(target, source, effect, requestedTicks, amplifier, true);
}
/** Applies an amplifier-zero external effect with one-time Arcane duration scaling. */
public static boolean applyScaledEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int requestedTicks) {
return applyScaledEffect(target, source, effect, requestedTicks, 0);
}
/** Adds one amplifier stack to an external effect, refreshes its duration, and tracks the latest source. */
public static boolean applyStackingEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int requestedTicks, int maximumStacks) {
return applyStackingEffect(target, source, effect, requestedTicks, maximumStacks, false, false);
}
/**
* Adds one stack to an external effect with configurable duration behavior and optional Arcane scaling.
* {@code maximumStacks} uses a human-facing one-based count; the applied amplifier is capped at one less.
* When {@code extendDuration} is true, the scaled requested duration is added to the remaining duration.
*/
public static boolean applyStackingEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int requestedTicks, int maximumStacks, boolean extendDuration,
boolean scaleDurationWithArcane) {
Objects.requireNonNull(effect, "effect");
if (!externalApplicationValid(target, source, effect, requestedTicks, 0)
|| maximumStacks < 1 || maximumStacks > 256) return false;
MobEffectInstance current = target.getEffect(effect);
if (current != null && current.isInfiniteDuration()) return false;
int amplifier = current == null ? 0 : current.getAmplifier() >= maximumStacks - 1
? maximumStacks - 1 : current.getAmplifier() + 1;
int duration = scaleDurationWithArcane ? AilmentScaling.scaleDuration(requestedTicks, source) : requestedTicks;
if (extendDuration && current != null) duration = safeAdd(current.getDuration(), duration);
boolean applied = addStackingEffect(target, source, effect, duration, amplifier, false, true, true);
if (applied) trackExternalSource(target, source, effect);
return applied;
}
/** Returns the UUID of the latest successful source tracked for an active external effect. */
@Nullable public static UUID getEffectSourceUuid(LivingEntity target, MobEffect effect) {
Objects.requireNonNull(target, "target");
Objects.requireNonNull(effect, "effect");
String key = externalSourceKey(effect);
if (key == null) return null;
if (!target.hasEffect(effect)) {
EffectSourceUtil.clear(target, key);
return null;
}
return EffectSourceUtil.getSourceUuid(target, key);
}
/** Returns the latest tracked source when it is currently loaded, or {@code null}. */
@Nullable public static LivingEntity getEffectSource(LivingEntity target, MobEffect effect) {
if (getEffectSourceUuid(target, effect) == null) return null;
return EffectSourceUtil.getSource(target, externalSourceKey(effect));
}
/** Resolves the latest tracked source only when it is loaded in the target's current level. */
@Nullable public static LivingEntity getEffectSourceInLevel(LivingEntity target, MobEffect effect) {
if (getEffectSourceUuid(target, effect) == null) return null;
return EffectSourceUtil.getSourceInLevel(target, externalSourceKey(effect));
}
/** Clears source metadata created by the generic external-effect API without removing the effect. */
public static void clearEffectSource(LivingEntity target, MobEffect effect) {
Objects.requireNonNull(target, "target");
Objects.requireNonNull(effect, "effect");
String key = externalSourceKey(effect);
if (key != null) EffectSourceUtil.clear(target, key);
}
/** Returns the registered MobEffect represented by a stable public ailment identifier. */ /** Returns the registered MobEffect represented by a stable public ailment identifier. */
public static MobEffect getEffect(AilmentType type) { public static MobEffect getEffect(AilmentType type) {
Objects.requireNonNull(type, "type"); Objects.requireNonNull(type, "type");
@@ -108,7 +202,8 @@ public final class AilmentApi {
/** Applies one Soul Rot stack; disabling extension always refreshes the configured base duration. */ /** Applies one Soul Rot stack; disabling extension always refreshes the configured base duration. */
public static boolean applySoulRot(LivingEntity target, @Nullable LivingEntity source, public static boolean applySoulRot(LivingEntity target, @Nullable LivingEntity source,
boolean extendWithStacks) { boolean extendWithStacks) {
if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.SOUL_ROT)) return false; if (!serverValid(target) || !canSoulRot(target)
|| !pvpProcReady(target, source, AilmentType.SOUL_ROT)) return false;
MobEffect effect = ModEffects.SOUL_ROT.get(); MobEffect effect = ModEffects.SOUL_ROT.get();
MobEffectInstance current = target.getEffect(effect); MobEffectInstance current = target.getEffect(effect);
int currentStacks = current == null ? 0 : current.getAmplifier() + 1; int currentStacks = current == null ? 0 : current.getAmplifier() + 1;
@@ -130,7 +225,8 @@ public final class AilmentApi {
/** Applies an explicit, capped Soul Rot amplifier and source-scaled requested duration. */ /** Applies an explicit, capped Soul Rot amplifier and source-scaled requested duration. */
public static boolean applySoulRot(LivingEntity target, @Nullable LivingEntity source, public static boolean applySoulRot(LivingEntity target, @Nullable LivingEntity source,
int requestedTicks, int amplifier) { int requestedTicks, int amplifier) {
if (!serverValid(target) || !pvpProcReady(target, source, AilmentType.SOUL_ROT)) return false; if (!serverValid(target) || !canSoulRot(target)
|| !pvpProcReady(target, source, AilmentType.SOUL_ROT)) return false;
int capped = Math.min(Math.max(0, amplifier), int capped = Math.min(Math.max(0, amplifier),
AilmentsConfig.value(AilmentsConfig.SOUL_ROT_MAX_STACKS) - 1); AilmentsConfig.value(AilmentsConfig.SOUL_ROT_MAX_STACKS) - 1);
return finishApplication(applyDot(target, source, ModEffects.SOUL_ROT.get(), EffectSourceUtil.SOUL_ROT, return finishApplication(applyDot(target, source, ModEffects.SOUL_ROT.get(), EffectSourceUtil.SOUL_ROT,
@@ -183,7 +279,7 @@ public final class AilmentApi {
*/ */
public static boolean applyFracture(LivingEntity target, @Nullable LivingEntity source, public static boolean applyFracture(LivingEntity target, @Nullable LivingEntity source,
int requestedTicks, int amplifier) { int requestedTicks, int amplifier) {
if (!serverValid(target) || requestedTicks < 1 || amplifier < 0 if (!serverValid(target) || !canFracture(target) || requestedTicks < 1 || amplifier < 0
|| !pvpProcReady(target, source, AilmentType.FRACTURE)) return false; || !pvpProcReady(target, source, AilmentType.FRACTURE)) return false;
MobEffect effect = ModEffects.FRACTURE.get(); MobEffect effect = ModEffects.FRACTURE.get();
MobEffectInstance current = target.getEffect(effect); MobEffectInstance current = target.getEffect(effect);
@@ -246,8 +342,16 @@ public final class AilmentApi {
} }
public static boolean canBleed(LivingEntity target) { public static boolean canBleed(LivingEntity target) {
if (target instanceof Player) return true; if (target.getType().is(ModEntityTypeTags.BLEED_IMMUNE)) return false;
return target.getMobType() != MobType.UNDEAD && !target.getType().is(ModEntityTypeTags.BLEED_IMMUNE); return target instanceof Player || target.getMobType() != MobType.UNDEAD;
}
/** Returns false for the Soulless tag and for any entity classified as undead by Minecraft or another mod. */
public static boolean canSoulRot(LivingEntity target) {
return target.getMobType() != MobType.UNDEAD && !target.getType().is(ModEntityTypeTags.SOULLESS);
}
/** Returns false for entity types in the Sturdy tag. */
public static boolean canFracture(LivingEntity target) {
return !target.getType().is(ModEntityTypeTags.STURDY);
} }
public static boolean isMentalControlResistant(LivingEntity target) { public static boolean isMentalControlResistant(LivingEntity target) {
return MentalControlUtil.isMentalControlResistant(target); return MentalControlUtil.isMentalControlResistant(target);
@@ -319,12 +423,30 @@ public final class AilmentApi {
} }
return applied; return applied;
} }
private static boolean applyExternalEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int requestedTicks, int amplifier, boolean scaleDurationWithArcane) {
Objects.requireNonNull(effect, "effect");
if (!externalApplicationValid(target, source, effect, requestedTicks, amplifier)) return false;
int duration = scaleDurationWithArcane ? AilmentScaling.scaleDuration(requestedTicks, source) : requestedTicks;
boolean applied = addSourcedEffect(target, source, effect, duration, amplifier, false, true, true);
if (applied) trackExternalSource(target, source, effect);
return applied;
}
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 MobEffectInstance instance(MobEffect effect, int duration, int amplifier, boolean ambient,
boolean visible, boolean icon) {
return new MobEffectInstance(effect, duration, amplifier, ambient, visible, icon);
}
private static boolean addSourcedEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, private static boolean addSourcedEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int duration, int amplifier, boolean icon) { int duration, int amplifier, boolean icon) {
boolean applied = target.addEffect(instance(effect, duration, amplifier, icon), source); return addSourcedEffect(target, source, effect, duration, amplifier, false, false, icon);
}
private static boolean addSourcedEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int duration, int amplifier, boolean ambient, boolean visible,
boolean icon) {
boolean applied = target.addEffect(instance(effect, duration, amplifier, ambient, visible, icon), source);
if (applied && AscendCompat.isAscendLoaded()) { if (applied && AscendCompat.isAscendLoaded()) {
// Ascend observes the standard Forge source and scales duration during addEffect. Ailments already // 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. // applied its source curve, so restore the requested duration to prevent double scaling.
@@ -335,19 +457,44 @@ public final class AilmentApi {
} }
private static boolean addStackingEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect, private static boolean addStackingEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int duration, int amplifier, boolean icon) { int duration, int amplifier, boolean icon) {
return addStackingEffect(target, source, effect, duration, amplifier, false, false, icon);
}
private static boolean addStackingEffect(LivingEntity target, @Nullable LivingEntity source, MobEffect effect,
int duration, int amplifier, boolean ambient, boolean visible,
boolean icon) {
MobEffectInstance previous = target.getEffect(effect); MobEffectInstance previous = target.getEffect(effect);
if (previous == null || previous.getAmplifier() >= amplifier) if (previous == null || previous.getAmplifier() >= amplifier)
return addSourcedEffect(target, source, effect, duration, amplifier, icon); return addSourcedEffect(target, source, effect, duration, amplifier, ambient, visible, icon);
// Vanilla preserves a lower-amplifier effect as a hidden fallback when upgrading. These ailments refresh // Vanilla preserves a lower-amplifier effect as a hidden fallback when upgrading. Stacking applications use
// to one authoritative stack state, so suppress that fallback without emitting a remove/add lifecycle. // one authoritative stack state, so suppress that fallback without emitting a remove/add lifecycle.
int previousDuration = previous.getDuration(); int previousDuration = previous.getDuration();
previous.mapDuration(ignored -> 0); previous.mapDuration(ignored -> 0);
boolean applied = addSourcedEffect(target, source, effect, duration, amplifier, icon); boolean applied = addSourcedEffect(target, source, effect, duration, amplifier, ambient, visible, icon);
if (!applied && target.getEffect(effect) == previous) if (!applied && target.getEffect(effect) == previous)
previous.mapDuration(ignored -> previousDuration); previous.mapDuration(ignored -> previousDuration);
return applied; return applied;
} }
private static boolean externalApplicationValid(LivingEntity target, @Nullable LivingEntity source,
MobEffect effect, int requestedTicks, int amplifier) {
if (!serverValid(target) || requestedTicks < 1 || amplifier < 0) return false;
if ((effect == ModEffects.BLEED.get() && !canBleed(target))
|| (effect == ModEffects.SOUL_ROT.get() && !canSoulRot(target))
|| (effect == ModEffects.FRACTURE.get() && !canFracture(target))) return false;
if (source != null && (!source.isAlive() || source.isSpectator()
|| source.level().isClientSide || source.level() != target.level())) return false;
return effect.getCategory() != MobEffectCategory.HARMFUL
|| !(source instanceof Player sourcePlayer && target instanceof Player targetPlayer)
|| source == target || sourcePlayer.canHarmPlayer(targetPlayer);
}
private static void trackExternalSource(LivingEntity target, @Nullable LivingEntity source, MobEffect effect) {
String key = externalSourceKey(effect);
if (key != null) EffectSourceUtil.setSource(target, key, source);
}
@Nullable private static String externalSourceKey(MobEffect effect) {
var id = ForgeRegistries.MOB_EFFECTS.getKey(effect);
return id == null ? null : "ascend_ailments.external." + id;
}
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());
@@ -1,5 +1,6 @@
package net.fretux.ailments.effect; package net.fretux.ailments.effect;
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.damage.ModDamageSources;
import net.fretux.ailments.util.EffectSourceUtil; import net.fretux.ailments.util.EffectSourceUtil;
@@ -20,6 +21,10 @@ public final class BleedEffect extends MobEffect {
@Override public void applyEffectTick(LivingEntity entity, int amplifier) { @Override public void applyEffectTick(LivingEntity entity, int amplifier) {
if (entity.level().isClientSide) return; if (entity.level().isClientSide) return;
if (!AilmentApi.canBleed(entity)) {
HemorrhageTracker.clearIfPresent(entity);
return;
}
double base = amplifier <= 0 ? AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_0) double base = amplifier <= 0 ? AilmentsConfig.value(AilmentsConfig.BLEED_DAMAGE_0)
: 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);
@@ -1,5 +1,6 @@
package net.fretux.ailments.effect; package net.fretux.ailments.effect;
import net.fretux.ailments.api.AilmentApi;
import net.fretux.ailments.config.AilmentsConfig; import net.fretux.ailments.config.AilmentsConfig;
import net.fretux.ailments.util.EffectSourceUtil; import net.fretux.ailments.util.EffectSourceUtil;
import net.minecraft.world.effect.MobEffect; import net.minecraft.world.effect.MobEffect;
@@ -13,7 +14,8 @@ public final class SoulRotEffect extends MobEffect {
@Override public boolean isDurationEffectTick(int duration, int amplifier) { return duration % 20 == 0; } @Override public boolean isDurationEffectTick(int duration, int amplifier) { return duration % 20 == 0; }
@Override public void applyEffectTick(LivingEntity entity, int amplifier) { @Override public void applyEffectTick(LivingEntity entity, int amplifier) {
if (entity.level().isClientSide || !(entity instanceof Player player)) return; if (entity.level().isClientSide || !AilmentApi.canSoulRot(entity)
|| !(entity instanceof Player player)) return;
float base = (float) (AilmentsConfig.value(AilmentsConfig.SOUL_ROT_EXHAUSTION_BASE) float base = (float) (AilmentsConfig.value(AilmentsConfig.SOUL_ROT_EXHAUSTION_BASE)
+ AilmentsConfig.value(AilmentsConfig.SOUL_ROT_EXHAUSTION_PER_AMP) * amplifier); + AilmentsConfig.value(AilmentsConfig.SOUL_ROT_EXHAUSTION_PER_AMP) * amplifier);
player.causeFoodExhaustion(base * EffectSourceUtil.getPotency(entity, EffectSourceUtil.SOUL_ROT)); player.causeFoodExhaustion(base * EffectSourceUtil.getPotency(entity, EffectSourceUtil.SOUL_ROT));
@@ -58,7 +58,8 @@ public final class AilmentEvents {
@SubscribeEvent @SubscribeEvent
public static void onHeal(LivingHealEvent event) { public static void onHeal(LivingHealEvent event) {
if (!event.getEntity().level().isClientSide && !event.isCanceled() if (!event.getEntity().level().isClientSide && !event.isCanceled()
&& event.getEntity().hasEffect(ModEffects.SOUL_ROT.get())) && event.getEntity().hasEffect(ModEffects.SOUL_ROT.get())
&& AilmentApi.canSoulRot(event.getEntity()))
event.setAmount((float) (event.getAmount() * AilmentsConfig.value(AilmentsConfig.SOUL_ROT_HEALING))); event.setAmount((float) (event.getAmount() * AilmentsConfig.value(AilmentsConfig.SOUL_ROT_HEALING)));
} }
@@ -97,7 +98,7 @@ public final class AilmentEvents {
float amount = event.getAmount(); float amount = event.getAmount();
// 1. Victim vulnerability. // 1. Victim vulnerability.
if (victim.hasEffect(ModEffects.SOUL_ROT.get())) if (victim.hasEffect(ModEffects.SOUL_ROT.get()) && AilmentApi.canSoulRot(victim))
amount *= (float) AilmentsConfig.value(AilmentsConfig.SOUL_ROT_DAMAGE_TAKEN); amount *= (float) AilmentsConfig.value(AilmentsConfig.SOUL_ROT_DAMAGE_TAKEN);
// 2. Charm victim and attacker rules. // 2. Charm victim and attacker rules.
@@ -26,8 +26,8 @@ public final class ControlEffectHelper {
public static void handleLivingTick(LivingEntity entity) { public static void handleLivingTick(LivingEntity entity) {
if (entity.level().isClientSide) return; if (entity.level().isClientSide) return;
handleSoulRotFood(entity);
cleanDotData(entity); cleanDotData(entity);
handleSoulRotFood(entity);
if (entity.hasEffect(ModEffects.FEAR.get())) tickFear(entity); if (entity.hasEffect(ModEffects.FEAR.get())) tickFear(entity);
else { else {
EffectSourceUtil.clear(entity, EffectSourceUtil.FEAR); EffectSourceUtil.clear(entity, EffectSourceUtil.FEAR);
@@ -181,9 +181,25 @@ public final class ControlEffectHelper {
} }
private static void cleanDotData(LivingEntity entity) { private static void cleanDotData(LivingEntity entity) {
if (!entity.hasEffect(ModEffects.SOUL_ROT.get())) EffectSourceUtil.clear(entity, EffectSourceUtil.SOUL_ROT); if (!AilmentApi.canSoulRot(entity)) {
if (!entity.hasEffect(ModEffects.BLEED.get())) EffectSourceUtil.clear(entity, EffectSourceUtil.BLEED); if (entity.hasEffect(ModEffects.SOUL_ROT.get())) entity.removeEffect(ModEffects.SOUL_ROT.get());
if (!entity.hasEffect(ModEffects.FRACTURE.get())) EffectSourceUtil.clear(entity, EffectSourceUtil.FRACTURE); EffectSourceUtil.clear(entity, EffectSourceUtil.SOUL_ROT);
} else if (!entity.hasEffect(ModEffects.SOUL_ROT.get())) {
EffectSourceUtil.clear(entity, EffectSourceUtil.SOUL_ROT);
}
if (!AilmentApi.canBleed(entity)) {
if (entity.hasEffect(ModEffects.BLEED.get())) entity.removeEffect(ModEffects.BLEED.get());
EffectSourceUtil.clear(entity, EffectSourceUtil.BLEED);
HemorrhageTracker.clearIfPresent(entity);
} else if (!entity.hasEffect(ModEffects.BLEED.get())) {
EffectSourceUtil.clear(entity, EffectSourceUtil.BLEED);
}
if (!AilmentApi.canFracture(entity)) {
if (entity.hasEffect(ModEffects.FRACTURE.get())) entity.removeEffect(ModEffects.FRACTURE.get());
EffectSourceUtil.clear(entity, EffectSourceUtil.FRACTURE);
} else if (!entity.hasEffect(ModEffects.FRACTURE.get())) {
EffectSourceUtil.clear(entity, EffectSourceUtil.FRACTURE);
}
} }
private ControlEffectHelper() {} private ControlEffectHelper() {}
} }
@@ -1,6 +1,7 @@
package net.fretux.ailments.util; package net.fretux.ailments.util;
import net.fretux.ailments.config.AilmentsConfig; import net.fretux.ailments.config.AilmentsConfig;
import net.fretux.ailments.api.AilmentApi;
import net.fretux.ailments.damage.ModDamageSources; import net.fretux.ailments.damage.ModDamageSources;
import net.fretux.ailments.network.AilmentNetwork; import net.fretux.ailments.network.AilmentNetwork;
import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.CompoundTag;
@@ -16,8 +17,12 @@ public final class HemorrhageTracker {
* Arcane potency is snapshotted when Bleed is applied, so an unloaded source cannot change an active meter. * Arcane potency is snapshotted when Bleed is applied, so an unloaded source cannot change an active meter.
*/ */
public static boolean recordBleedDamage(LivingEntity target, int amplifier, double arcanePotency) { public static boolean recordBleedDamage(LivingEntity target, int amplifier, double arcanePotency) {
if (target.level().isClientSide if (target.level().isClientSide) return false;
|| amplifier + 1 < AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_MINIMUM_STACKS)) return false; if (!AilmentApi.canBleed(target)) {
clear(target);
return false;
}
if (amplifier + 1 < AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_MINIMUM_STACKS)) return false;
double threshold = AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_THRESHOLD); double threshold = AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_THRESHOLD);
double added = AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_FILL_PER_TICK) double added = AilmentsConfig.value(AilmentsConfig.HEMORRHAGE_FILL_PER_TICK)
* Math.max(0.0, arcanePotency); * Math.max(0.0, arcanePotency);
@@ -41,6 +46,10 @@ public final class HemorrhageTracker {
} }
public static double getProgress(LivingEntity target) { public static double getProgress(LivingEntity target) {
if (!AilmentApi.canBleed(target)) {
clearIfPresent(target);
return 0.0;
}
CompoundTag data = target.getPersistentData(); CompoundTag data = target.getPersistentData();
return data.contains(PROGRESS) ? Math.max(0.0, data.getDouble(PROGRESS)) : 0.0; return data.contains(PROGRESS) ? Math.max(0.0, data.getDouble(PROGRESS)) : 0.0;
} }
@@ -55,6 +64,12 @@ public final class HemorrhageTracker {
hideDisplay(target); hideDisplay(target);
} }
/** Clears stored buildup and its HUD only when buildup actually exists. */
public static void clearIfPresent(LivingEntity target) {
if (!target.getPersistentData().contains(PROGRESS)) return;
clear(target);
}
/** Synchronizes the compact client HUD after login or a meter update. */ /** Synchronizes the compact client HUD after login or a meter update. */
public static void syncDisplay(LivingEntity target) { public static void syncDisplay(LivingEntity target) {
if (!(target instanceof ServerPlayer player)) return; if (!(target instanceof ServerPlayer player)) return;
@@ -8,6 +8,8 @@ import net.minecraft.world.entity.EntityType;
public final class ModEntityTypeTags { public final class ModEntityTypeTags {
public static final TagKey<EntityType<?>> BLEED_IMMUNE = tag("bleed_immune"); public static final TagKey<EntityType<?>> BLEED_IMMUNE = tag("bleed_immune");
public static final TagKey<EntityType<?>> SOULLESS = tag("soulless");
public static final TagKey<EntityType<?>> STURDY = tag("sturdy");
public static final TagKey<EntityType<?>> MENTAL_CONTROL_RESISTANT = tag("mental_control_resistant"); public static final TagKey<EntityType<?>> MENTAL_CONTROL_RESISTANT = tag("mental_control_resistant");
public static final TagKey<EntityType<?>> MENTAL_CONTROL_IMMUNE = tag("mental_control_immune"); public static final TagKey<EntityType<?>> MENTAL_CONTROL_IMMUNE = tag("mental_control_immune");
private static TagKey<EntityType<?>> tag(String path) { private static TagKey<EntityType<?>> tag(String path) {