Roll rework / refactor Fixes #5023

This commit is contained in:
nossr50
2024-09-14 14:21:48 -07:00
parent b87efb3f76
commit 5cc97383fa
11 changed files with 365 additions and 78 deletions

View File

@ -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 };
}

View File

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