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);