diff --git a/Changelog.txt b/Changelog.txt index 4ba5bcbdf..46169d31a 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,13 @@ +Version 2.2.020 + (Codebase) Reworked Roll implementation (See notes) + (Codebase) Added unit test coverage for Roll + Fixed a bug where Roll was modifying damage unnecessarily + + NOTES: + The code for Roll was a bit of a mess, I've rewritten a good chunk of it and added some unit test coverage. + I will likely put out another update for Acrobatics in general, as the code for Acrobatics is whack. + This would be a good time to suggest changes to Acrobatics on discord. + Version 2.2.019 Optimized Alchemy code (thanks MrPowerGamerBR) Fixed an exception that could occur when shooting entities through worlds (thanks Wariorrrr) diff --git a/pom.xml b/pom.xml index 9bed4bf53..329127934 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.gmail.nossr50.mcMMO mcMMO - 2.2.019 + 2.2.020-SNAPSHOT mcMMO https://github.com/mcMMO-Dev/mcMMO diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java b/src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java index 97706a989..ebe3b0c72 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java @@ -11,13 +11,9 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.EventUtils; import com.gmail.nossr50.util.ItemUtils; import com.gmail.nossr50.util.Permissions; -import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.random.Probability; import com.gmail.nossr50.util.random.ProbabilityUtil; -import com.gmail.nossr50.util.skills.PerksUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillUtils; -import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -34,9 +30,17 @@ import org.jetbrains.annotations.VisibleForTesting; import java.util.Locale; +import static com.gmail.nossr50.util.player.NotificationManager.sendPlayerInformation; +import static com.gmail.nossr50.util.random.ProbabilityUtil.getSubSkillProbability; +import static com.gmail.nossr50.util.skills.SkillUtils.applyXpGain; +import static com.gmail.nossr50.util.sounds.SoundManager.sendCategorizedSound; + public class Roll extends AcrobaticsSubSkill { + public static final String GRACEFUL_ROLL_ACTIVATED_LOCALE_STR_KEY = "Acrobatics.Ability.Proc"; + public static final String ROLL_ACTIVATED_LOCALE_KEY = "Acrobatics.Roll.Text"; + public Roll() { super("Roll", EventPriority.HIGHEST, SubSkillType.ACROBATICS_ROLL); } @@ -49,23 +53,14 @@ public class Roll extends AcrobaticsSubSkill { */ @Override public boolean doInteraction(Event event, mcMMO plugin) { - //TODO: Go through and API this up - - /* - * Roll is a SubSkill which allows players to negate fall damage from certain heights with sufficient Acrobatics skill and luck - * Roll is activated when a player takes damage from a fall - * If a player holds shift, they double their odds at a successful roll and upon success are told they did a graceful roll. - */ - - //Casting - EntityDamageEvent entityDamageEvent = (EntityDamageEvent) event; + final EntityDamageEvent entityDamageEvent = (EntityDamageEvent) event; //Make sure a real player was damaged in this event if (!EventUtils.isRealPlayerDamaged(entityDamageEvent)) return false; - if (entityDamageEvent.getCause() == EntityDamageEvent.DamageCause.FALL) {//Grab the player - McMMOPlayer mmoPlayer = EventUtils.getMcMMOPlayer(entityDamageEvent.getEntity()); + if (entityDamageEvent.getCause() == EntityDamageEvent.DamageCause.FALL) { + final McMMOPlayer mmoPlayer = EventUtils.getMcMMOPlayer(entityDamageEvent.getEntity()); if (mmoPlayer == null) return false; @@ -75,16 +70,38 @@ public class Roll extends AcrobaticsSubSkill { */ if (canRoll(mmoPlayer)) { - entityDamageEvent.setDamage( - rollCheck(mmoPlayer, entityDamageEvent.getFinalDamage(), mmoPlayer.getPlayer().isSneaking())); + final RollResult rollResult + = rollCheck(mmoPlayer, entityDamageEvent); + if (rollResult == null) { + // no-op - fall was fatal or otherwise did not get processed + return false; + } + entityDamageEvent.setDamage(rollResult.getModifiedDamage()); if (entityDamageEvent.getFinalDamage() == 0) { entityDamageEvent.setCancelled(true); - return true; } + + // Roll happened, send messages and XP + if (rollResult.isRollSuccess()) { + final String key + = rollResult.isGraceful() ? GRACEFUL_ROLL_ACTIVATED_LOCALE_STR_KEY : ROLL_ACTIVATED_LOCALE_KEY; + sendPlayerInformation(mmoPlayer.getPlayer(), NotificationType.SUBSKILL_MESSAGE, key); + sendCategorizedSound(mmoPlayer.getPlayer(), mmoPlayer.getPlayer().getLocation(), + SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS,0.5F); + } + + if (!rollResult.isExploiting() && rollResult.getXpGain() > 0) { + applyXpGain(mmoPlayer, getPrimarySkill(), rollResult.getXpGain(), XPGainReason.PVE); + } + + // Player didn't die, so add the location to the list + addFallLocation(mmoPlayer); + return true; + // We give Acrobatics XP for fall damage even if they haven't unlocked roll } else if (mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(mmoPlayer.getPlayer(), PrimarySkillType.ACROBATICS)) { - //Give XP Anyways - SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, ((EntityDamageEvent) event).getFinalDamage(), false), XPGainReason.PVE); + //Give XP + applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, ((EntityDamageEvent) event).getFinalDamage(), false), XPGainReason.PVE); } } @@ -167,7 +184,7 @@ public class Roll extends AcrobaticsSubSkill { @NotNull private Probability getRollProbability(McMMOPlayer mmoPlayer) { - return ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer); + return getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer); } @Override @@ -185,54 +202,67 @@ public class Roll extends AcrobaticsSubSkill { return true; } - private boolean canRoll(McMMOPlayer mmoPlayer) { + @VisibleForTesting + public boolean canRoll(McMMOPlayer mmoPlayer) { return RankUtils.hasUnlockedSubskill(mmoPlayer.getPlayer(), SubSkillType.ACROBATICS_ROLL) && Permissions.isSubSkillEnabled(mmoPlayer.getPlayer(), SubSkillType.ACROBATICS_ROLL); } - private int getActivationChance(McMMOPlayer mmoPlayer) { - return PerksUtils.handleLuckyPerks(mmoPlayer, getPrimarySkill()); - } - /** * Handle the damage reduction and XP gain from the Roll / Graceful Roll ability * - * @param damage The amount of damage initially dealt by the event + * @param entityDamageEvent the event to modify in the event of roll success * @return the modified event damage if the ability was successful, the original event damage otherwise */ - private double rollCheck(McMMOPlayer mmoPlayer, double damage, boolean isGracefulRoll) { + @VisibleForTesting + public RollResult rollCheck(McMMOPlayer mmoPlayer, EntityDamageEvent entityDamageEvent) { + double baseDamage = entityDamageEvent.getDamage(); + final boolean isGraceful = mmoPlayer.getPlayer().isSneaking(); + final RollResult.Builder rollResultBuilder + = new RollResult.Builder(entityDamageEvent, isGraceful); final Probability probability - = isGracefulRoll ? getGracefulProbability(mmoPlayer) : getNonGracefulProbability(mmoPlayer); - double modifiedDamage = calculateModifiedRollDamage(damage, - mcMMO.p.getAdvancedConfig().getRollDamageThreshold() * 2); + = isGraceful ? getGracefulProbability(mmoPlayer) : getNonGracefulProbability(mmoPlayer); + double modifiedDamage = calculateModifiedRollDamage(baseDamage, + mcMMO.p.getAdvancedConfig().getRollDamageThreshold() * 2); + rollResultBuilder.modifiedDamage(modifiedDamage); + + boolean isExploiting = isPlayerExploitingAcrobatics(mmoPlayer); + rollResultBuilder.exploiting(isExploiting); + // They Rolled if (!isFatal(mmoPlayer, modifiedDamage) && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.ACROBATICS, mmoPlayer, probability)) { - NotificationManager.sendPlayerInformation(mmoPlayer.getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Ability.Proc"); - SoundManager.sendCategorizedSound(mmoPlayer.getPlayer(), mmoPlayer.getPlayer().getLocation(), SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS,0.5F); - if (!isExploiting(mmoPlayer) && mmoPlayer.getAcrobaticsManager().canGainRollXP()) - SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, damage, true), XPGainReason.PVE); + rollResultBuilder.rollSuccess(true); + rollResultBuilder.exploiting(isExploiting); + final boolean canGainXp = mmoPlayer.getAcrobaticsManager().canGainRollXP(); - addFallLocation(mmoPlayer); - return modifiedDamage; - } else if (!isFatal(mmoPlayer, damage)) { - if (!isExploiting(mmoPlayer) && mmoPlayer.getAcrobaticsManager().canGainRollXP()) - SkillUtils.applyXpGain(mmoPlayer, getPrimarySkill(), calculateRollXP(mmoPlayer, damage, false), XPGainReason.PVE); - - addFallLocation(mmoPlayer); + if (!isExploiting && canGainXp) { + rollResultBuilder.xpGain((int) calculateRollXP(mmoPlayer, baseDamage, true)); + } + + return rollResultBuilder.build(); + // They did not roll, but they also did not die so reward XP as appropriate + } else if (!isFatal(mmoPlayer, baseDamage)) { + rollResultBuilder.rollSuccess(false); + final boolean canGainXp = mmoPlayer.getAcrobaticsManager().canGainRollXP(); + if (!isExploiting && canGainXp) { + rollResultBuilder.xpGain((int) calculateRollXP(mmoPlayer, baseDamage, false)); + } + + return rollResultBuilder.build(); } - - return damage; + // Fall was fatal return null + return null; } @NotNull public static Probability getGracefulProbability(McMMOPlayer mmoPlayer) { - double gracefulOdds = ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer).getValue() * 2; + double gracefulOdds = getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer).getValue() * 2; return Probability.ofValue(gracefulOdds); } public static Probability getNonGracefulProbability(McMMOPlayer mmoPlayer) { - double gracefulOdds = ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer).getValue(); + double gracefulOdds = getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, mmoPlayer).getValue(); return Probability.ofValue(gracefulOdds); } @@ -242,7 +272,7 @@ public class Roll extends AcrobaticsSubSkill { * * @return true if exploits are detected, false otherwise */ - private boolean isExploiting(McMMOPlayer mmoPlayer) { + private boolean isPlayerExploitingAcrobatics(McMMOPlayer mmoPlayer) { if (!ExperienceConfig.getInstance().isAcrobaticsExploitingPrevented()) { return false; } @@ -331,11 +361,9 @@ public class Roll extends AcrobaticsSubSkill { */ @Override public Double[] getStats(McMMOPlayer mmoPlayer) { - double playerChanceRoll = ProbabilityUtil.getSubSkillProbability(subSkillType, mmoPlayer).getValue(); + double playerChanceRoll = getSubSkillProbability(subSkillType, mmoPlayer).getValue(); double playerChanceGrace = playerChanceRoll * 2; - double gracefulOdds = ProbabilityUtil.getSubSkillProbability(subSkillType, mmoPlayer).getValue() * 2; - return new Double[]{ playerChanceRoll, playerChanceGrace }; } diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/RollResult.java b/src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/RollResult.java new file mode 100644 index 000000000..e2ee88578 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/RollResult.java @@ -0,0 +1,115 @@ +package com.gmail.nossr50.datatypes.skills.subskills.acrobatics; + +import org.bukkit.event.entity.EntityDamageEvent; + +import static java.util.Objects.requireNonNull; + +/** + * Immutable class representing the result of a roll action in acrobatics. + */ +public class RollResult { + private final boolean rollSuccess; + private final boolean isGraceful; + private final double eventDamage; + private final double modifiedDamage; + private final boolean isFatal; + private final boolean isExploiting; + private final float xpGain; + + private RollResult(Builder builder) { + this.rollSuccess = builder.rollSuccess; + this.isGraceful = builder.isGraceful; + this.eventDamage = builder.eventDamage; + this.modifiedDamage = builder.modifiedDamage; + this.isFatal = builder.isFatal; + this.isExploiting = builder.isExploiting; + this.xpGain = builder.xpGain; + } + + public boolean isRollSuccess() { + return rollSuccess; + } + + public boolean isGraceful() { + return isGraceful; + } + + public double getEventDamage() { + return eventDamage; + } + + public double getModifiedDamage() { + return modifiedDamage; + } + + public boolean isFatal() { + return isFatal; + } + + public boolean isExploiting() { + return isExploiting; + } + + public float getXpGain() { + return xpGain; + } + + /** + * Builder class for constructing {@code RollResult} instances. + */ + public static class Builder { + private final boolean isGraceful; + private final double eventDamage; + private double modifiedDamage; + private boolean isFatal; + private boolean rollSuccess; + private boolean isExploiting; + private float xpGain; + + /** + * Constructs a new {@code Builder} with required parameters. + * + * @param entityDamageEvent the damage event, must not be null + * @param isGracefulRoll whether the roll is graceful + */ + public Builder(EntityDamageEvent entityDamageEvent, boolean isGracefulRoll) { + requireNonNull(entityDamageEvent, "EntityDamageEvent cannot be null"); + this.eventDamage = entityDamageEvent.getDamage(); + this.isGraceful = isGracefulRoll; + } + + public Builder modifiedDamage(double modifiedDamage) { + this.modifiedDamage = modifiedDamage; + return this; + } + + public Builder fatal(boolean isFatal) { + this.isFatal = isFatal; + return this; + } + + public Builder rollSuccess(boolean rollSuccess) { + this.rollSuccess = rollSuccess; + return this; + } + + public Builder exploiting(boolean isExploiting) { + this.isExploiting = isExploiting; + return this; + } + + public Builder xpGain(float xpGain) { + this.xpGain = xpGain; + return this; + } + + /** + * Builds and returns a {@code RollResult} instance. + * + * @return a new {@code RollResult} + */ + public RollResult build() { + return new RollResult(this); + } + } +} diff --git a/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java b/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java index 3baa1a98d..72f883a68 100644 --- a/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java +++ b/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java @@ -15,11 +15,16 @@ import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.blockmeta.ChunkManager; import com.gmail.nossr50.util.compat.CompatibilityManager; import com.gmail.nossr50.util.platform.MinecraftGameVersion; +import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.skills.SkillTools; +import com.gmail.nossr50.util.sounds.SoundManager; import org.bukkit.*; +import org.bukkit.attribute.Attribute; +import org.bukkit.block.Block; import org.bukkit.entity.Player; +import org.bukkit.event.Event; import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; @@ -44,6 +49,8 @@ public abstract class MMOTestEnvironment { protected MockedStatic mockedMisc; protected MockedStatic mockedSkillTools; protected MockedStatic mockedEventUtils; + protected MockedStatic notificationManager; + protected MockedStatic mockedSoundManager; protected TransientEntityTracker transientEntityTracker; protected AdvancedConfig advancedConfig; protected PartyConfig partyConfig; @@ -63,7 +70,6 @@ public abstract class MMOTestEnvironment { protected PlayerInventory playerInventory; protected PlayerProfile playerProfile; protected McMMOPlayer mmoPlayer; - protected String playerName = "testPlayer"; protected ItemFactory itemFactory; protected ChunkManager chunkManager; @@ -124,14 +130,25 @@ public abstract class MMOTestEnvironment { this.server = mock(Server.class); when(mcMMO.p.getServer()).thenReturn(server); + // wire Bukkit mockedBukkit = mockStatic(Bukkit.class); when(Bukkit.getItemFactory()).thenReturn(itemFactory); itemFactory = mock(ItemFactory.class); - // when(itemFactory.getItemMeta(any())).thenReturn(mock(ItemMeta.class)); + + // wire Bukkit call to get server + when(Bukkit.getServer()).thenReturn(server); // wire plugin manager this.pluginManager = mock(PluginManager.class); + // wire server -> plugin manager when(server.getPluginManager()).thenReturn(pluginManager); + // wire Bukkit -> plugin manager + when(Bukkit.getPluginManager()).thenReturn(pluginManager); + // return the argument provided when call event is invoked on plugin manager mock + doAnswer(invocation -> { + Object[] args = invocation.getArguments(); + return args[0]; + }).when(pluginManager).callEvent(any(Event.class)); // wire world this.world = mock(World.class); @@ -143,10 +160,19 @@ public abstract class MMOTestEnvironment { // setup player and player related mocks after everything else this.player = mock(Player.class); when(player.getUniqueId()).thenReturn(playerUUID); - + when(player.isValid()).thenReturn(true); + when(player.isOnline()).thenReturn(true); + // health + when(player.getHealth()).thenReturn(20D); // wire inventory this.playerInventory = mock(PlayerInventory.class); when(player.getInventory()).thenReturn(playerInventory); + // player location + Location playerLocation = mock(Location.class); + Block playerLocationBlock = mock(Block.class); + when(player.getLocation()).thenReturn(playerLocation); + when(playerLocation.getBlock()).thenReturn(playerLocationBlock); + // when(playerLocationBlock.getType()).thenReturn(Material.AIR); // PlayerProfile and McMMOPlayer are partially mocked playerProfile = new PlayerProfile("testPlayer", player.getUniqueId(), 0); @@ -158,6 +184,12 @@ public abstract class MMOTestEnvironment { this.materialMapStore = new MaterialMapStore(); when(mcMMO.getMaterialMapStore()).thenReturn(materialMapStore); + + // wire notification manager + notificationManager = mockStatic(NotificationManager.class); + + // wire sound manager + mockedSoundManager = mockStatic(SoundManager.class); } private void mockPermissions() { @@ -201,7 +233,7 @@ public abstract class MMOTestEnvironment { when(ExperienceConfig.getInstance().getCombatXP("Cow")).thenReturn(1D); } - protected void cleanupBaseEnvironment() { + protected void cleanUpStaticMocks() { // Clean up resources here if needed. if (mockedMcMMO != null) { mockedMcMMO.close(); @@ -230,5 +262,11 @@ public abstract class MMOTestEnvironment { if (mockedBukkit != null) { mockedBukkit.close(); } + if (notificationManager != null) { + notificationManager.close(); + } + if (mockedSoundManager != null) { + mockedSoundManager.close(); + } } } diff --git a/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java b/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java index 0d8f01ccb..5bf59751e 100644 --- a/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java +++ b/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java @@ -12,12 +12,13 @@ import org.mockito.Mockito; import java.util.UUID; import java.util.logging.Logger; +import static java.util.logging.Logger.getLogger; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; class PartyManagerTest extends MMOTestEnvironment { - private static final Logger logger = Logger.getLogger(PartyManagerTest.class.getName()); + private static final Logger logger = getLogger(PartyManagerTest.class.getName()); @BeforeEach public void setUp() { @@ -29,7 +30,7 @@ class PartyManagerTest extends MMOTestEnvironment { @AfterEach public void tearDown() { - cleanupBaseEnvironment(); + cleanUpStaticMocks(); // disable parties in config for other tests Mockito.when(partyConfig.isPartyEnabled()).thenReturn(false); diff --git a/src/test/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsTest.java b/src/test/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsTest.java new file mode 100644 index 000000000..3bcc77b45 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsTest.java @@ -0,0 +1,101 @@ +package com.gmail.nossr50.skills.acrobatics; + +import com.gmail.nossr50.MMOTestEnvironment; +import com.gmail.nossr50.api.exceptions.InvalidSkillException; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill; +import com.gmail.nossr50.datatypes.skills.subskills.acrobatics.Roll; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.skills.RankUtils; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDamageEvent; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.logging.Logger; + +import static java.util.logging.Logger.getLogger; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.*; + +class AcrobaticsTest extends MMOTestEnvironment { + private static final Logger logger = getLogger(AcrobaticsTest.class.getName()); + + @BeforeEach + void setUp() throws InvalidSkillException { + mockBaseEnvironment(logger); + when(rankConfig.getSubSkillUnlockLevel(SubSkillType.ACROBATICS_ROLL, 1)).thenReturn(1); + when(rankConfig.getSubSkillUnlockLevel(SubSkillType.ACROBATICS_DODGE, 1)).thenReturn(1); + + // wire advanced config + when(advancedConfig.getMaximumProbability(SubSkillType.ACROBATICS_ROLL)).thenReturn(100D); + when(advancedConfig.getMaxBonusLevel(SubSkillType.ACROBATICS_ROLL)).thenReturn(1000); + when(advancedConfig.getRollDamageThreshold()).thenReturn(7D); + + Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.ACROBATICS_ROLL, 1)).thenReturn(1); // needed? + Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.ACROBATICS_DODGE, 1)).thenReturn(1000); // needed? + + when(RankUtils.getRankUnlockLevel(SubSkillType.ACROBATICS_ROLL, 1)).thenReturn(1); // needed? + when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.ACROBATICS_ROLL))).thenReturn(true); + when(RankUtils.hasReachedRank(eq(1), any(Player.class), any(AbstractSubSkill.class))).thenReturn(true); + } + + @AfterEach + void tearDown() { + cleanUpStaticMocks(); + } + + @SuppressWarnings("deprecation") + @Test + public void rollShouldLowerDamage() { + // Given + final Roll roll = new Roll(); + final double damage = 2D; + final EntityDamageEvent mockEvent = mockEntityDamageEvent(damage); + mmoPlayer.modifySkill(PrimarySkillType.ACROBATICS, 1000); + when(roll.canRoll(mmoPlayer)).thenReturn(true); + assertThat(roll.canRoll(mmoPlayer)).isTrue(); + + // When + roll.doInteraction(mockEvent, mcMMO.p); + + // Then + verify(mockEvent, atLeastOnce()).setDamage(0); + } + + @SuppressWarnings("deprecation") + @Test + public void rollShouldNotLowerDamage() { + // Given + final Roll roll = new Roll(); + final double damage = 100D; + final EntityDamageEvent mockEvent = mockEntityDamageEvent(damage); + mmoPlayer.modifySkill(PrimarySkillType.ACROBATICS, 0); + when(roll.canRoll(mmoPlayer)).thenReturn(true); + assertThat(roll.canRoll(mmoPlayer)).isTrue(); + + // When + roll.doInteraction(mockEvent, mcMMO.p); + + // Then + assertThat(roll.canRoll(mmoPlayer)).isTrue(); + verify(mockEvent, Mockito.never()).setDamage(any(Double.class)); + } + + private @NotNull EntityDamageEvent mockEntityDamageEvent(double damage) { + final EntityDamageEvent mockEvent = mock(EntityDamageEvent.class); + when(mockEvent.getCause()).thenReturn(EntityDamageEvent.DamageCause.FALL); + when(mockEvent.getFinalDamage()).thenReturn(damage); + when(mockEvent.getDamage(any(EntityDamageEvent.DamageModifier.class))).thenReturn(damage); + when(mockEvent.getDamage()).thenReturn(damage); + when(mockEvent.isCancelled()).thenReturn(false); + when(mockEvent.getEntity()).thenReturn(player); + return mockEvent; + } +} \ No newline at end of file diff --git a/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java b/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java index 631696f0a..c3593f038 100644 --- a/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java +++ b/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java @@ -13,7 +13,6 @@ import org.bukkit.block.BlockState; import org.bukkit.block.data.BlockData; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -29,7 +28,6 @@ import static org.mockito.Mockito.*; class ExcavationTest extends MMOTestEnvironment { private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(ExcavationTest.class.getName()); - @BeforeEach void setUp() throws InvalidSkillException { mockBaseEnvironment(logger); @@ -48,18 +46,13 @@ class ExcavationTest extends MMOTestEnvironment { when(player.getUniqueId()).thenReturn(playerUUID); // wire inventory - this.playerInventory = Mockito.mock(PlayerInventory.class); this.itemInMainHand = new ItemStack(Material.DIAMOND_SHOVEL); - when(player.getInventory()).thenReturn(playerInventory); when(playerInventory.getItemInMainHand()).thenReturn(itemInMainHand); - - // Set up spy for Excavation Manager - } @AfterEach void tearDown() { - cleanupBaseEnvironment(); + cleanUpStaticMocks(); } @Test diff --git a/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java b/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java index 199c8e554..ae6bb191e 100644 --- a/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java +++ b/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java @@ -10,8 +10,12 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.mockito.Mockito; +import java.util.logging.Logger; + +import static java.util.logging.Logger.getLogger; + class TridentsTest extends MMOTestEnvironment { - private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(TridentsTest.class.getName()); + private static final Logger logger = getLogger(TridentsTest.class.getName()); TridentsManager tridentsManager; ItemStack trident; @@ -34,6 +38,6 @@ class TridentsTest extends MMOTestEnvironment { @AfterEach void tearDown() { - cleanupBaseEnvironment(); + cleanUpStaticMocks(); } } diff --git a/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java b/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java index 06eecb43b..781cb68b1 100644 --- a/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java +++ b/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java @@ -11,21 +11,23 @@ import org.bukkit.block.Block; import org.bukkit.block.BlockState; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.PlayerInventory; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.Collections; +import java.util.logging.Logger; +import static java.util.logging.Logger.getLogger; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; class WoodcuttingTest extends MMOTestEnvironment { - private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(WoodcuttingTest.class.getName()); + private static final Logger logger = getLogger(WoodcuttingTest.class.getName()); + + private WoodcuttingManager woodcuttingManager; - WoodcuttingManager woodcuttingManager; @BeforeEach void setUp() throws InvalidSkillException { mockBaseEnvironment(logger); @@ -42,12 +44,7 @@ class WoodcuttingTest extends MMOTestEnvironment { Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_HARVEST_LUMBER))).thenReturn(true); Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_CLEAN_CUTS))).thenReturn(true); - // setup player and player related mocks after everything else - this.player = Mockito.mock(Player.class); - Mockito.when(player.getUniqueId()).thenReturn(playerUUID); - // wire inventory - this.playerInventory = Mockito.mock(PlayerInventory.class); this.itemInMainHand = new ItemStack(Material.DIAMOND_AXE); Mockito.when(player.getInventory()).thenReturn(playerInventory); Mockito.when(playerInventory.getItemInMainHand()).thenReturn(itemInMainHand); @@ -58,7 +55,7 @@ class WoodcuttingTest extends MMOTestEnvironment { @AfterEach void tearDown() { - cleanupBaseEnvironment(); + cleanUpStaticMocks(); } @Test diff --git a/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java b/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java index 905a46b4d..a78a3ffa7 100644 --- a/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java +++ b/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java @@ -38,7 +38,7 @@ class ProbabilityUtilTest extends MMOTestEnvironment { @AfterEach public void tearDown() { - cleanupBaseEnvironment(); + cleanUpStaticMocks(); } private static Stream staticChanceSkills() {