diff --git a/src/main/java/com/gmail/nossr50/api/AbilityAPI.java b/src/main/java/com/gmail/nossr50/api/AbilityAPI.java index 80d100a5e..6f44745fd 100644 --- a/src/main/java/com/gmail/nossr50/api/AbilityAPI.java +++ b/src/main/java/com/gmail/nossr50/api/AbilityAPI.java @@ -2,7 +2,7 @@ package com.gmail.nossr50.api; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.SuperAbilityType; -import com.gmail.nossr50.util.MetadataConstants; +import com.gmail.nossr50.runnables.skills.RuptureTask; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -110,10 +110,6 @@ public final class AbilityAPI { } public static boolean isBleeding(LivingEntity entity) { - if (entity.isValid()) { - return entity.hasMetadata(MetadataConstants.METADATA_KEY_RUPTURE); - } - - return false; + return entity.isValid() && RuptureTask.getActive(entity) != null; } } diff --git a/src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java b/src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java index 9563449af..1ace0ac46 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java +++ b/src/main/java/com/gmail/nossr50/datatypes/meta/RuptureTaskMeta.java @@ -1,10 +1,22 @@ package com.gmail.nossr50.datatypes.meta; +import com.gmail.nossr50.api.AbilityAPI; import com.gmail.nossr50.runnables.skills.RuptureTask; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.plugin.Plugin; import org.jetbrains.annotations.NotNull; +/** + * No longer used. mcMMO stopped attaching rupture state to entities as Bukkit metadata + * in 2.3.001; active ruptures are tracked internally instead. Use + * {@link AbilityAPI#isBleeding(LivingEntity)} to check for an active bleed, or + * {@link RuptureTask#getActive(Entity)} to retrieve the task. + * + * @deprecated mcMMO no longer creates or reads this metadata. + */ +@Deprecated(forRemoval = true, since = "2.3.001") public class RuptureTaskMeta extends FixedMetadataValue { private final @NotNull RuptureTask ruptureTask; diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java index d6fe4ab82..f4e9f2858 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java @@ -12,7 +12,6 @@ import com.gmail.nossr50.datatypes.chat.ChatChannel; import com.gmail.nossr50.datatypes.experience.XPGainReason; import com.gmail.nossr50.datatypes.experience.XPGainSource; import com.gmail.nossr50.datatypes.interactions.NotificationType; -import com.gmail.nossr50.datatypes.meta.RuptureTaskMeta; import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.party.PartyTeleportRecord; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; @@ -48,7 +47,6 @@ import com.gmail.nossr50.skills.unarmed.UnarmedManager; import com.gmail.nossr50.skills.woodcutting.WoodcuttingManager; import com.gmail.nossr50.util.BlockUtils; import com.gmail.nossr50.util.EventUtils; -import com.gmail.nossr50.util.MetadataConstants; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.experience.ExperienceBarManager; @@ -1296,17 +1294,9 @@ public class McMMOPlayer implements Identified { */ public void logout(boolean syncSave) { final Player thisPlayer = getPlayer(); - if (thisPlayer.hasMetadata(MetadataConstants.METADATA_KEY_RUPTURE)) { - final RuptureTaskMeta ruptureTaskMeta - = (RuptureTaskMeta) thisPlayer.getMetadata( - MetadataConstants.METADATA_KEY_RUPTURE).get(0); - if (ruptureTaskMeta != null) { - final RuptureTask ruptureTimerTask = ruptureTaskMeta.getRuptureTimerTask(); - if (ruptureTimerTask != null) { - ruptureTimerTask.cancel(); - } - thisPlayer.removeMetadata(MetadataConstants.METADATA_KEY_RUPTURE, mcMMO.p); - } + final RuptureTask ruptureTask = RuptureTask.getActive(thisPlayer); + if (ruptureTask != null) { + ruptureTask.cancel(); } cleanup(); diff --git a/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java b/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java index 28e15da37..dde529fe5 100644 --- a/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java @@ -6,21 +6,29 @@ import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.events.skills.rupture.McMMOEntityDamageByRuptureEvent; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.CancellableRunnable; -import com.gmail.nossr50.util.MetadataConstants; import com.gmail.nossr50.util.MobHealthbarUtils; import com.gmail.nossr50.util.skills.ParticleEffectUtils; import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import org.bukkit.attribute.AttributeInstance; +import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public class RuptureTask extends CancellableRunnable { public static final int DAMAGE_TICK_INTERVAL = 10; public static final int ANIMATION_TICK_INTERVAL = 1; + // Active rupture per target entity. + private static final @NotNull Map ACTIVE_RUPTURES = + new ConcurrentHashMap<>(); + private final @NotNull McMMOPlayer ruptureSource; private final @NotNull LivingEntity targetEntity; private final int expireTick; @@ -53,6 +61,20 @@ public class RuptureTask extends CancellableRunnable { this.pureTickDamage = pureTickDamage; } + public static @Nullable RuptureTask getActive(@NotNull Entity target) { + return ACTIVE_RUPTURES.get(target.getUniqueId()); + } + + /** + * Registers this rupture as the target's active bleed and starts it ticking. + * The task removes itself from the registry when it is cancelled; on Folia the + * retired callback covers entities that are removed before the next tick. + */ + public void schedule() { + ACTIVE_RUPTURES.put(targetEntity.getUniqueId(), this); + mcMMO.p.getFoliaLib().getScheduler().runAtEntityTimer(targetEntity, this, this::cancel, 1, 1); + } + /** * Deprecated constructor for the RuptureTask class. * @@ -106,10 +128,15 @@ public class RuptureTask extends CancellableRunnable { } } else { this.cancel(); //Task no longer needed - targetEntity.removeMetadata(MetadataConstants.METADATA_KEY_RUPTURE, mcMMO.p); } } + @Override + public void cancel() { + ACTIVE_RUPTURES.remove(targetEntity.getUniqueId(), this); + super.cancel(); + } + private void playAnimation() { if (animationTick >= ANIMATION_TICK_INTERVAL) { ParticleEffectUtils.playBleedEffect(targetEntity); //Animate @@ -176,7 +203,6 @@ public class RuptureTask extends CancellableRunnable { } private void endRupture() { - targetEntity.removeMetadata(MetadataConstants.METADATA_KEY_RUPTURE, mcMMO.p); this.cancel(); //Task no longer needed } 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 faa796af4..ea3ba5da0 100644 --- a/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java +++ b/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java @@ -1,7 +1,6 @@ package com.gmail.nossr50.skills.swords; import com.gmail.nossr50.datatypes.interactions.NotificationType; -import com.gmail.nossr50.datatypes.meta.RuptureTaskMeta; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; @@ -10,7 +9,6 @@ import com.gmail.nossr50.datatypes.skills.ToolType; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.runnables.skills.RuptureTask; import com.gmail.nossr50.skills.SkillManager; -import com.gmail.nossr50.util.MetadataConstants; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.random.ProbabilityUtil; @@ -81,17 +79,15 @@ public class SwordsManager extends SkillManager { return; } - if (target.hasMetadata(MetadataConstants.METADATA_KEY_RUPTURE)) { - RuptureTaskMeta ruptureTaskMeta = (RuptureTaskMeta) target.getMetadata( - MetadataConstants.METADATA_KEY_RUPTURE).get(0); - + final RuptureTask ongoingRupture = RuptureTask.getActive(target); + if (ongoingRupture != null) { if (mmoPlayer.isDebugMode()) { mmoPlayer.getPlayer() .sendMessage("Rupture task ongoing for target " + target); - mmoPlayer.getPlayer().sendMessage(ruptureTaskMeta.getRuptureTimerTask().toString()); + mmoPlayer.getPlayer().sendMessage(ongoingRupture.toString()); } - ruptureTaskMeta.getRuptureTimerTask().refreshRupture(); + ongoingRupture.refreshRupture(); return; //Don't apply bleed } @@ -118,10 +114,7 @@ public class SwordsManager extends SkillManager { mcMMO.p.getAdvancedConfig() .getRuptureTickDamage(target instanceof Player, getRuptureRank())); - final RuptureTaskMeta ruptureTaskMeta = new RuptureTaskMeta(mcMMO.p, ruptureTask); - - mcMMO.p.getFoliaLib().getScheduler().runAtEntityTimer(target, ruptureTask, 1, 1); - target.setMetadata(MetadataConstants.METADATA_KEY_RUPTURE, ruptureTaskMeta); + ruptureTask.schedule(); } } diff --git a/src/main/java/com/gmail/nossr50/util/MetadataConstants.java b/src/main/java/com/gmail/nossr50/util/MetadataConstants.java index 89252c16a..bb7e8f04e 100644 --- a/src/main/java/com/gmail/nossr50/util/MetadataConstants.java +++ b/src/main/java/com/gmail/nossr50/util/MetadataConstants.java @@ -17,7 +17,6 @@ public class MetadataConstants { * removed by {@link MobHealthbarUtils#restoreNameFromSnapshot}. */ public static final @NotNull Set MOB_METADATA_KEYS = Set.of( - MetadataConstants.METADATA_KEY_RUPTURE, MetadataConstants.METADATA_KEY_EXPLOSION_FROM_RUPTURE ); @@ -66,7 +65,17 @@ public class MetadataConstants { public static final @NotNull String METADATA_KEY_VILLAGER_TRADE_ORIGIN_ITEM = "mcmmo_villager_trade_origin_item"; public static final @NotNull String METADATA_KEY_EXPLOITED_ENDERMEN = "mcmmo_exploited_endermen"; + /** + * No longer used. mcMMO stopped applying this metadata to bleeding entities in 2.3.001, + * so {@code hasMetadata} checks against this key always return false. Use + * {@link com.gmail.nossr50.api.AbilityAPI#isBleeding(org.bukkit.entity.LivingEntity)} instead. + * + * @deprecated mcMMO no longer applies this metadata; see + * {@link com.gmail.nossr50.api.AbilityAPI#isBleeding}. + */ + @Deprecated(forRemoval = true, since = "2.3.001") public static final @NotNull String METADATA_KEY_RUPTURE = "mcmmo_rupture"; + public static final byte SIMPLE_FLAG_VALUE = (byte) 0x1; /** diff --git a/src/test/java/com/gmail/nossr50/skills/swords/SwordsManagerTest.java b/src/test/java/com/gmail/nossr50/skills/swords/SwordsManagerTest.java index 120c00910..55b8f208f 100644 --- a/src/test/java/com/gmail/nossr50/skills/swords/SwordsManagerTest.java +++ b/src/test/java/com/gmail/nossr50/skills/swords/SwordsManagerTest.java @@ -14,14 +14,12 @@ import static org.mockito.Mockito.when; import com.gmail.nossr50.MMOTestEnvironment; import com.gmail.nossr50.api.exceptions.InvalidSkillException; import com.gmail.nossr50.datatypes.interactions.NotificationType; -import com.gmail.nossr50.datatypes.meta.RuptureTaskMeta; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.SuperAbilityType; import com.gmail.nossr50.datatypes.skills.ToolType; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.runnables.skills.RuptureTask; -import com.gmail.nossr50.util.MetadataConstants; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.random.ProbabilityUtil; @@ -29,7 +27,9 @@ import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; import com.tcoded.folialib.FoliaLib; import com.tcoded.folialib.impl.PlatformScheduler; -import java.util.List; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.UUID; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -67,6 +67,7 @@ class SwordsManagerTest extends MMOTestEnvironment { swordsManager = new SwordsManager(mmoPlayer); target = Mockito.mock(LivingEntity.class); + when(target.getUniqueId()).thenReturn(UUID.randomUUID()); // Simulate Paper 26.1.2+ during a damage event: the ticker was already reset, so the // live cooldown misreports the charge of the committed hit. @@ -86,7 +87,6 @@ class SwordsManagerTest extends MMOTestEnvironment { .thenReturn(true); Mockito.when(RankUtils.getRank(player, SubSkillType.SWORDS_RUPTURE)).thenReturn(3); when(advancedConfig.getRuptureChanceToApplyOnHit(3)).thenReturn(33.0); - when(target.hasMetadata(MetadataConstants.METADATA_KEY_RUPTURE)).thenReturn(false); try (MockedStatic probabilityUtil = mockStatic(ProbabilityUtil.class)) { // When - a hit committed at half attack strength lands and the roll fails @@ -171,18 +171,21 @@ class SwordsManagerTest extends MMOTestEnvironment { when(advancedConfig.getRuptureChanceToApplyOnHit(3)).thenReturn(100.0); } + // Seeds RuptureTask's active-rupture registry for the test. + @SuppressWarnings("unchecked") + private void trackRupture(Entity target, RuptureTask task) throws Exception { + Field field = RuptureTask.class.getDeclaredField("ACTIVE_RUPTURES"); + field.setAccessible(true); + ((Map) field.get(null)).put(target.getUniqueId(), task); + } + @Test - void existingRuptureShouldBeRefreshedInsteadOfReRolled() { + void existingRuptureShouldBeRefreshedInsteadOfReRolled() throws Exception { try (MockedStatic probabilityUtil = mockStatic(ProbabilityUtil.class)) { // Given - the target is already bleeding from a rupture final RuptureTask ongoingRupture = Mockito.mock(RuptureTask.class); - final RuptureTaskMeta ruptureMeta = Mockito.mock(RuptureTaskMeta.class); - when(ruptureMeta.getRuptureTimerTask()).thenReturn(ongoingRupture); - when(target.hasMetadata(MetadataConstants.METADATA_KEY_RUPTURE)) - .thenReturn(true); - when(target.getMetadata(MetadataConstants.METADATA_KEY_RUPTURE)) - .thenReturn(List.of(ruptureMeta)); + trackRupture(target, ongoingRupture); // When - another rupture-capable hit lands swordsManager.processRupture(target, 1.0); @@ -207,10 +210,9 @@ class SwordsManagerTest extends MMOTestEnvironment { swordsManager.processRupture(target, 1.0); // Then - a bleed task starts ticking on the target and is remembered on it - verify(scheduler).runAtEntityTimer(eq(target), any(RuptureTask.class), eq(1L), - eq(1L)); - verify(target).setMetadata(eq(MetadataConstants.METADATA_KEY_RUPTURE), - any(RuptureTaskMeta.class)); + verify(scheduler).runAtEntityTimer(eq(target), any(RuptureTask.class), + any(Runnable.class), eq(1L), eq(1L)); + assertThat(RuptureTask.getActive(target)).isNotNull(); } } @@ -220,6 +222,7 @@ class SwordsManagerTest extends MMOTestEnvironment { mockStatic(ProbabilityUtil.class)) { // Given - a defending player who is blocking with a shield final Player defender = Mockito.mock(Player.class); + when(defender.getUniqueId()).thenReturn(UUID.randomUUID()); when(defender.isBlocking()).thenReturn(true); probabilityUtil.when(() -> ProbabilityUtil.isStaticSkillRNGSuccessful( eq(PrimarySkillType.SWORDS), eq(mmoPlayer), eq(100.0))) @@ -229,8 +232,7 @@ class SwordsManagerTest extends MMOTestEnvironment { swordsManager.processRupture(defender, 1.0); // Then - no bleed starts - verify(defender, never()).setMetadata(eq(MetadataConstants.METADATA_KEY_RUPTURE), - any()); + assertThat(RuptureTask.getActive(defender)).isNull(); } } @@ -240,6 +242,7 @@ class SwordsManagerTest extends MMOTestEnvironment { mockStatic(ProbabilityUtil.class)) { // Given - a non-blocking defending player who uses notifications final Player defender = Mockito.mock(Player.class); + when(defender.getUniqueId()).thenReturn(UUID.randomUUID()); when(NotificationManager.doesPlayerUseNotifications(defender)).thenReturn(true); probabilityUtil.when(() -> ProbabilityUtil.isStaticSkillRNGSuccessful( eq(PrimarySkillType.SWORDS), eq(mmoPlayer), eq(100.0)))