mirror of
				https://github.com/mcMMO-Dev/mcMMO.git
				synced 2025-11-04 11:03:43 +01:00 
			
		
		
		
	Roll rework / refactor Fixes #5023
This commit is contained in:
		@@ -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 };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user