diff --git a/Changelog.txt b/Changelog.txt
index b95ab27ff..e8a174645 100644
--- a/Changelog.txt
+++ b/Changelog.txt
@@ -1,3 +1,8 @@
+Version 2.2.039
+ Added StackOverflow safeguards for abilities dealing damage in mcMMO
+ Improved compatibility with MythicMobs/ModelEngine
+ Improved compatibility with AdvancedEnchantments
+
Version 2.2.038
Fix potion match failing for non-english locales
FoliaLib Performance improvements (thanks SirSalad)
diff --git a/pom.xml b/pom.xml
index f8a4fd0ce..445dccd52 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2,7 +2,7 @@
4.0.0
com.gmail.nossr50.mcMMO
mcMMO
- 2.2.038
+ 2.2.039-SNAPSHOT
mcMMO
https://github.com/mcMMO-Dev/mcMMO
diff --git a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java
index 3d576d859..005d4d159 100644
--- a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java
+++ b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java
@@ -296,10 +296,6 @@ public class EntityListener implements Listener {
// However, for entities, we do not wanna cancel this event to allow plugins to observe changes
// properly
- if (CombatUtils.isProcessingNoInvulnDamage()) {
- return;
- }
-
if (event.getEntity() instanceof ArmorStand) {
return;
}
diff --git a/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java b/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java
index c39ea1919..2418e52b1 100644
--- a/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java
+++ b/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java
@@ -572,7 +572,8 @@ public class FishingManager extends SkillManager {
ItemUtils.spawnItem(getPlayer(), target.getLocation(), drop, ItemSpawnReason.FISHING_SHAKE_TREASURE);
// Make it so you can shake a mob no more than 4 times.
- CombatUtils.dealDamage(target, Math.min(Math.max(target.getMaxHealth() / 4, 1), 10), getPlayer());
+ double dmg = Math.min(Math.max(target.getMaxHealth() / 4, 1), 10);
+ CombatUtils.safeDealDamage(target, dmg, getPlayer());
applyXpGain(ExperienceConfig.getInstance().getFishingShakeXP(), XPGainReason.PVE);
}
}
diff --git a/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java b/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java
index 2b70f90ec..c232e61b3 100644
--- a/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java
+++ b/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java
@@ -122,12 +122,14 @@ public class SwordsManager extends SkillManager {
*/
public void counterAttackChecks(@NotNull LivingEntity attacker, double damage) {
if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.SWORDS_COUNTER_ATTACK, mmoPlayer)) {
- CombatUtils.dealDamage(attacker, damage / Swords.counterAttackModifier, getPlayer());
+ CombatUtils.safeDealDamage(attacker, damage / Swords.counterAttackModifier, getPlayer());
- NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Countered");
+ NotificationManager.sendPlayerInformation(getPlayer(),
+ NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Countered");
if (attacker instanceof Player) {
- NotificationManager.sendPlayerInformation((Player)attacker, NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Counter.Hit");
+ NotificationManager.sendPlayerInformation((Player)attacker,
+ NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Counter.Hit");
}
}
}
diff --git a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java
index e30f0cca0..58d13c37e 100644
--- a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java
+++ b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java
@@ -159,7 +159,8 @@ public class WoodcuttingManager extends SkillManager {
double health = player.getHealth();
if (health > 1) {
- CombatUtils.dealDamage(player, Misc.getRandom().nextInt((int) (health - 1)));
+ int dmg = Misc.getRandom().nextInt((int) (health - 1));
+ CombatUtils.safeDealDamage(player, dmg);
}
return;
diff --git a/src/main/java/com/gmail/nossr50/util/ChimaeraWing.java b/src/main/java/com/gmail/nossr50/util/ChimaeraWing.java
index 49025c647..d5c010755 100644
--- a/src/main/java/com/gmail/nossr50/util/ChimaeraWing.java
+++ b/src/main/java/com/gmail/nossr50/util/ChimaeraWing.java
@@ -95,7 +95,8 @@ public final class ChimaeraWing {
NotificationManager.sendPlayerInformation(player,
NotificationType.REQUIREMENTS_NOT_MET, "Item.ChimaeraWing.Fail");
player.setVelocity(new Vector(0, 0.5D, 0));
- CombatUtils.dealDamage(player, Misc.getRandom().nextInt((int) (player.getHealth() - 10)));
+ final int dmg = Misc.getRandom().nextInt((int) (player.getHealth() - 10));
+ CombatUtils.safeDealDamage(player, dmg);
mcMMOPlayer.actualizeChimeraWingLastUse();
return;
}
diff --git a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
index c4da7481b..1dc0e9746 100644
--- a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
+++ b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
@@ -43,8 +43,50 @@ import static com.gmail.nossr50.util.skills.ProjectileUtils.isCrossbowProjectile
public final class CombatUtils {
+ private static final ThreadLocal IN_MCMO_DAMAGE
+ = ThreadLocal.withInitial(() -> false);
+
+
+ public static void safeDealDamage(@NotNull LivingEntity target, double amount) {
+ safeDealDamage(target, amount, null);
+ }
+
+ /**
+ * Safely deals damage to a target entity, preventing recursive mcMMO damage calls.
+ *
+ * @param target The {@link LivingEntity} to damage.
+ * @param amount The amount of damage to deal.
+ * @param attacker The {@link Entity} responsible for the damage, or null if none.
+ */
+ public static void safeDealDamage(@NotNull LivingEntity target, double amount, @Nullable Entity attacker) {
+ boolean prev = IN_MCMO_DAMAGE.get();
+
+ if (prev || target.isDead()) {
+ return;
+ }
+
+ try {
+ IN_MCMO_DAMAGE.set(true);
+ if (!hasIgnoreDamageMetadata(target)) {
+ applyIgnoreDamageMetadata(target);
+ }
+
+ if (attacker != null) {
+ target.damage(amount, attacker);
+ } else {
+ target.damage(amount);
+ }
+ } finally {
+ IN_MCMO_DAMAGE.set(false);
+ if (hasIgnoreDamageMetadata(target)) {
+ removeIgnoreDamageMetadata(target);
+ }
+ }
+ }
+
private CombatUtils() {}
+ @Deprecated(forRemoval = true, since = "2.2.039")
public static boolean isDamageLikelyFromNormalCombat(@NotNull DamageCause damageCause) {
return switch (damageCause) {
case ENTITY_ATTACK, ENTITY_SWEEP_ATTACK, PROJECTILE -> true;
@@ -52,6 +94,7 @@ public final class CombatUtils {
};
}
+ @Deprecated(forRemoval = true, since = "2.2.039")
public static boolean hasWeakenedDamage(@NotNull LivingEntity livingEntity) {
return livingEntity.hasPotionEffect(PotionEffectType.WEAKNESS);
}
@@ -150,7 +193,10 @@ public final class CombatUtils {
printFinalDamageDebug(player, event, mcMMOPlayer);
}
- private static void processTridentCombatRanged(@NotNull Trident trident, @NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
+ private static void processTridentCombatRanged(@NotNull Trident trident,
+ @NotNull LivingEntity target,
+ @NotNull Player player,
+ @NotNull EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) {
return;
}
@@ -620,7 +666,8 @@ public final class CombatUtils {
* @param subSkillType the specific limit break skill for calculations
* @return the RAW damage bonus from Limit Break which is applied before reductions
*/
- public static int getLimitBreakDamage(@NotNull Player attacker, @NotNull LivingEntity defender, @NotNull SubSkillType subSkillType) {
+ public static int getLimitBreakDamage(@NotNull Player attacker, @NotNull LivingEntity defender,
+ @NotNull SubSkillType subSkillType) {
if (defender instanceof Player playerDefender) {
return getLimitBreakDamageAgainstQuality(attacker, subSkillType, getArmorQualityLevel(playerDefender));
} else {
@@ -636,18 +683,20 @@ public final class CombatUtils {
* @param armorQualityLevel Armor quality level
* @return the RAW damage boost after its been mutated by armor quality
*/
- public static int getLimitBreakDamageAgainstQuality(@NotNull Player attacker, @NotNull SubSkillType subSkillType, int armorQualityLevel) {
- int rawDamageBoost = RankUtils.getRank(attacker, subSkillType);
+ public static int getLimitBreakDamageAgainstQuality(@NotNull Player attacker,
+ @NotNull SubSkillType subSkillType,
+ int armorQualityLevel) {
+ float rawDamageBoost = RankUtils.getRank(attacker, subSkillType);
if (armorQualityLevel <= 4) {
- rawDamageBoost *= .25; //75% Nerf
+ rawDamageBoost *= .25F; //75% Nerf
} else if (armorQualityLevel <= 8) {
- rawDamageBoost *= .50; //50% Nerf
+ rawDamageBoost *= .50F; //50% Nerf
} else if (armorQualityLevel <= 12) {
- rawDamageBoost *= .75; //25% Nerf
+ rawDamageBoost *= .75F; //25% Nerf
}
- return rawDamageBoost;
+ return (int) rawDamageBoost;
}
/**
@@ -695,9 +744,11 @@ public final class CombatUtils {
*
* @param target LivingEntity which to attempt to damage
* @param damage Amount of damage to attempt to do
+ * @deprecated use {@link #safeDealDamage(LivingEntity, double, Entity)} instead
*/
+ @Deprecated(since = "2.2.039")
public static void dealDamage(@NotNull LivingEntity target, double damage) {
- dealDamage(target, damage, null);
+ safeDealDamage(target, damage, null);
}
/**
@@ -706,53 +757,11 @@ public final class CombatUtils {
* @param target the entity to attempt to damage
* @param damage Amount of damage to attempt to do
* @param attacker the responsible entity (nullable)
+ * @deprecated use {@link #safeDealDamage(LivingEntity, double, Entity)} instead
*/
+ @Deprecated(since = "2.2.039")
public static void dealDamage(@NotNull LivingEntity target, double damage, @Nullable Entity attacker) {
- if (target.isDead()) {
- return;
- }
- try {
- // TODO: Check for Spigot API for DamageSource, if DamageSource is found then send out a damage() with a custom damage source
- applyIgnoreDamageMetadata(target);
- if (attacker != null) {
- target.damage(damage, attacker);
- } else {
- target.damage(damage);
- }
- } finally {
- removeIgnoreDamageMetadata(target);
- }
- }
-
- private static boolean processingNoInvulnDamage;
- public static boolean isProcessingNoInvulnDamage() {
- return processingNoInvulnDamage;
- }
-
- public static void dealNoInvulnerabilityTickDamage(@NotNull LivingEntity target, double damage, @Nullable Entity attacker) {
- if (target.isDead()) {
- return;
- }
-
- // TODO: This is horrible, but there is no cleaner way to do this without potentially breaking existing code right now
- // calling damage here is a double edged sword: On one hand, without a call, plugins won't see this properly when the entity dies,
- // potentially mis-attributing the death cause; calling a fake event would partially fix this, but this and setting the last damage
- // cause do have issues around plugin observability. This is not a perfect solution, but it appears to be the best one here
- // We also set no damage ticks to 0, to ensure that damage is applied for this case, and reset it back to the original value
- // Snapshot current state so we can pop up properly
- boolean wasMetaSet = hasIgnoreDamageMetadata(target);
- boolean wasProcessing = processingNoInvulnDamage;
- // set markers
- processingNoInvulnDamage = true;
- applyIgnoreDamageMetadata(target);
- int noDamageTicks = target.getNoDamageTicks();
- target.setNoDamageTicks(0);
- target.damage(damage, attacker);
- target.setNoDamageTicks(noDamageTicks);
- if (!wasMetaSet)
- removeIgnoreDamageMetadata(target);
- if (!wasProcessing)
- processingNoInvulnDamage = false;
+ safeDealDamage(target, damage, attacker);
}
public static void removeIgnoreDamageMetadata(@NotNull LivingEntity target) {
@@ -767,14 +776,6 @@ public final class CombatUtils {
return target.hasMetadata(MetadataConstants.METADATA_KEY_CUSTOM_DAMAGE);
}
- public static void dealNoInvulnerabilityTickDamageRupture(@NotNull LivingEntity target, double damage, Entity attacker, int toolTier) {
- if (target.isDead()) {
- return;
- }
-
- dealNoInvulnerabilityTickDamage(target, damage, attacker);
- }
-
/**
* Apply Area-of-Effect ability actions.
* @param attacker The attacking player
@@ -826,7 +827,7 @@ public final class CombatUtils {
break;
}
- dealDamage(livingEntity, damageAmount, attacker);
+ safeDealDamage(livingEntity, damageAmount, attacker);
numberOfTargets--;
}
}
@@ -860,7 +861,8 @@ public final class CombatUtils {
if (target instanceof Player defender) {
if (!ExperienceConfig.getInstance().getExperienceGainsPlayerVersusPlayerEnabled()
||
- (mcMMO.p.getPartyConfig().isPartyEnabled() && mcMMO.p.getPartyManager().inSameParty(mcMMOPlayer.getPlayer(), (Player) target))) {
+ (mcMMO.p.getPartyConfig().isPartyEnabled()
+ && mcMMO.p.getPartyManager().inSameParty(mcMMOPlayer.getPlayer(), (Player) target))) {
return;
}
@@ -1041,6 +1043,7 @@ public final class CombatUtils {
MobHealthbarUtils.handleMobHealthbars(target, damage, plugin);
}
+ @Deprecated(forRemoval = true, since = "2.2.039")
public static void modifyMoveSpeed(@NotNull LivingEntity livingEntity, double multiplier) {
AttributeInstance attributeInstance = livingEntity.getAttribute(MAPPED_MOVEMENT_SPEED);