Endgame Update (#4840)

General
Added Crossbows Skill, this skill is a WIP and feedback on discord is appreciated.
Added Tridents Skill, this skill is a WIP and feedback on discord is appreciated.
Added the "endgame" triple drop subskill 'Mother Lode' to Mining
Added the "endgame" triple drop subskill 'Clean Cuts' to Woodcutting
Added the "endgame" triple drop subskill 'Verdant Bounty' to Herbalism
Added /mmopower command which simply shows your power level (aliases /mmopowerlevel /powerlevel)

Config
Added 'Send_To_Console' settings to chat.yml to toggle sending party or admin chat messages to console.
Replaced 'Experience_Formula.Modifier' in experience.yml with 'Experience_Formula.Skill_Multiplier' which is easier to understand and less prone to divide by zero bugs.
child.yml config is gone now, feel free to delete it.

Tweaks
Tree Feller now drops 90% less non-wood block rewards (leaves/etc) on average from Knock on Wood.
Treasure drop rate from Shake, Fishing, Hylian, and Excavation now benefit from the Luck perk.
Updated advanced.yml with entries for the new skills

Permission nodes
Added 'mcmmo.commands.mmopower' permission node for the new /mmopower command
Added 'mcmmo.commands.crossbows' permission node
Added 'mcmmo.ability.crossbows.crossbowslimitbreak' permission node
Added 'mcmmo.ability.crossbows.trickshot' permission node
Added 'mcmmo.ability.herbalism.verdantbounty' permission node
Added 'mcmmo.ability.mining.motherlode' permission node
Added 'mcmmo.ability.woodcutting.cleancuts' permission node

Locale
Added locale entries for motherlode, cleancuts, and verdant bounty.

Codebase
Major rewrite for how random chance was handled in the code.
Many skills with RNG elements now send out a SubSkillEvent (which can be used to modify probability or cancel the results), some skills without RNG still send out this event when activated, this event is cancellable so it can be used to make a skill fail.
A lot of new unit tests were added to help keep mcMMO stable as part of this update, of course, more could always be added.

NOTES:
One feature of this update is to provide an endgame benefits to some skills that you can grind for a long time, ideally for a long while. I will likely expand upon this idea in future updates.
A few skills have these endgame-oriented subskills, these new subskills provide a small benefit at first that grows and scales up to level 10,000 (or 1,000 for Standard mode which no one uses) and does not have ranks (other than the initial rank to unlock it).
These endgame sub skills unlock at level 1000 for users with default mcMMO settings, or 100 for those using the optional Standard scaling.
You can tweak the benefits of these skills in advanced.yml, the default settings are meant to be a good starting point.

Crossbows and Tridents are WIP skills, I would like feedback on discord about them.

More info on the new Triple Drop skills (Mother Lode, Clean Cuts, Verdant Bounty):
Currently these start at about 5%  chance and can reach a maximum 50% chance if a player acquired 10,000 skill, you can adjust this in advanced.yml
These skills respect double drop settings from config.yml just like the corresponding Double Drop skills do, if a double drop is disabled for an item, then it's disabled for triple drops too.
I added a new Power Level Command, for now this just shows you your current power level. If I ever add features based on power level, this command will likely display output related to those features.

Regarding Maces, I will likely add that as a WIP skill when the next Minecraft update drops.
This commit is contained in:
Robert Alan Chapton
2024-03-30 06:09:59 -07:00
committed by GitHub
parent bead5feb14
commit 2594dc1bca
141 changed files with 3586 additions and 1991 deletions

View File

@@ -10,13 +10,13 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.metadata.MobMetaFlagType;
import com.gmail.nossr50.metadata.MobMetadataService;
import com.gmail.nossr50.party.PartyManager;
import com.gmail.nossr50.runnables.skills.AwardCombatXpTask;
import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager;
import com.gmail.nossr50.skills.archery.ArcheryManager;
import com.gmail.nossr50.skills.axes.AxesManager;
import com.gmail.nossr50.skills.swords.SwordsManager;
import com.gmail.nossr50.skills.taming.TamingManager;
import com.gmail.nossr50.skills.tridents.TridentsManager;
import com.gmail.nossr50.skills.unarmed.UnarmedManager;
import com.gmail.nossr50.util.*;
import com.gmail.nossr50.util.player.NotificationManager;
@@ -45,7 +45,6 @@ public final class CombatUtils {
return mcMMO.getMetadataService().getMobMetadataService();
}
//Likely.. because who knows what plugins are throwing around
public static boolean isDamageLikelyFromNormalCombat(@NotNull DamageCause damageCause) {
return switch (damageCause) {
case ENTITY_ATTACK, ENTITY_SWEEP_ATTACK, PROJECTILE -> true;
@@ -112,6 +111,78 @@ public final class CombatUtils {
}
}
}
private static void processTridentCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) {
return;
}
double boostedDamage = event.getDamage();
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Make sure the profiles been loaded
if(mcMMOPlayer == null) {
return;
}
TridentsManager tridentsManager = mcMMOPlayer.getTridentsManager();
if (tridentsManager.canActivateAbility()) {
mcMMOPlayer.checkAbilityActivation(PrimarySkillType.TRIDENTS);
}
if (SkillUtils.canUseSubskill(player, SubSkillType.TRIDENTS_IMPALE)) {
boostedDamage += (tridentsManager.impaleDamageBonus() * mcMMOPlayer.getAttackStrength());
}
if(canUseLimitBreak(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK)) {
boostedDamage += (getLimitBreakDamage(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK) * mcMMOPlayer.getAttackStrength());
}
event.setDamage(boostedDamage);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.TRIDENTS);
printFinalDamageDebug(player, event, mcMMOPlayer);
}
private static void processCrossbowsCombat(@NotNull LivingEntity target, @NotNull Player player,
@NotNull EntityDamageByEntityEvent event, @NotNull Arrow arrow) {
double initialDamage = event.getDamage();
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Make sure the profiles been loaded
if(mcMMOPlayer == null) {
delayArrowMetaCleanup(arrow);
return;
}
double boostedDamage = event.getDamage();
if (SkillUtils.canUseSubskill(player, SubSkillType.CROSSBOWS_POWERED_SHOT)) {
//Not Additive
boostedDamage = mcMMOPlayer.getCrossbowsManager().poweredShot(initialDamage);
}
if(canUseLimitBreak(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK)) {
boostedDamage+=getLimitBreakDamage(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK);
}
double distanceMultiplier = ArcheryManager.distanceXpBonusMultiplier(target, arrow);
double forceMultiplier = 1.0;
event.setDamage(boostedDamage);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.CROSSBOWS, forceMultiplier * distanceMultiplier);
printFinalDamageDebug(player, event, mcMMOPlayer,
"Distance Multiplier: "+distanceMultiplier,
"Force Multiplier: "+forceMultiplier,
"Initial Damage: "+initialDamage,
"Final Damage: "+boostedDamage);
//Clean data
delayArrowMetaCleanup(arrow);
}
private static void processAxeCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) {
@@ -240,14 +311,15 @@ public final class CombatUtils {
}
private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event, @NotNull Projectile arrow) {
private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull Player player,
@NotNull EntityDamageByEntityEvent event, @NotNull Arrow arrow) {
double initialDamage = event.getDamage();
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Make sure the profiles been loaded
if(mcMMOPlayer == null) {
cleanupArrowMetadata(arrow);
delayArrowMetaCleanup(arrow);
return;
}
@@ -273,7 +345,7 @@ public final class CombatUtils {
boostedDamage+=getLimitBreakDamage(player, target, SubSkillType.ARCHERY_ARCHERY_LIMIT_BREAK);
}
double distanceMultiplier = archeryManager.distanceXpBonusMultiplier(target, arrow);
double distanceMultiplier = ArcheryManager.distanceXpBonusMultiplier(target, arrow);
double forceMultiplier = 1.0; //Hacky Fix - some plugins spawn arrows and assign them to players after the ProjectileLaunchEvent fires
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE))
@@ -288,7 +360,7 @@ public final class CombatUtils {
"Initial Damage: "+initialDamage,
"Final Damage: "+boostedDamage);
//Clean data
cleanupArrowMetadata(arrow);
delayArrowMetaCleanup(arrow);
}
/**
@@ -383,6 +455,15 @@ public final class CombatUtils {
processUnarmedCombat(target, player, event);
}
}
else if (ItemUtils.isTrident(heldItem)) {
if (!mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.TRIDENTS, target)) {
return;
}
if (mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TRIDENTS)) {
processTridentCombat(target, player, event);
}
}
}
else if (entityType == EntityType.WOLF) {
@@ -396,20 +477,25 @@ public final class CombatUtils {
}
}
}
else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) {
Projectile arrow = (Projectile) painSource;
else if (painSource instanceof Arrow arrow) {
ProjectileSource projectileSource = arrow.getShooter();
boolean isCrossbow = arrow.isShotFromCrossbow();
if (projectileSource instanceof Player player) {
if (projectileSource instanceof Player player && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
if (!Misc.isNPCEntityExcludingVillagers(player) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.ARCHERY)) {
processArcheryCombat(target, player, event, arrow);
if (!Misc.isNPCEntityExcludingVillagers(player)) {
if(!isCrossbow && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
processArcheryCombat(target, player, event, arrow);
} else if(isCrossbow && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.CROSSBOWS, target)) {
processCrossbowsCombat(target, player, event, arrow);
}
} else {
//Cleanup Arrow
cleanupArrowMetadata(arrow);
delayArrowMetaCleanup(arrow);
}
if (target.getType() != EntityType.CREEPER && !Misc.isNPCEntityExcludingVillagers(player) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TAMING)) {
if (target.getType() != EntityType.CREEPER
&& !Misc.isNPCEntityExcludingVillagers(player)
&& mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TAMING)) {
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
if(mcMMOPlayer == null)
@@ -420,7 +506,6 @@ public final class CombatUtils {
}
}
}
}
/**
@@ -621,7 +706,7 @@ public final class CombatUtils {
}
public static boolean hasIgnoreDamageMetadata(@NotNull LivingEntity target) {
return target.getMetadata(MetadataConstants.METADATA_KEY_CUSTOM_DAMAGE).size() != 0;
return target.hasMetadata(MetadataConstants.METADATA_KEY_CUSTOM_DAMAGE);
}
public static void dealNoInvulnerabilityTickDamageRupture(@NotNull LivingEntity target, double damage, Entity attacker, int toolTier) {
@@ -630,35 +715,6 @@ public final class CombatUtils {
}
dealNoInvulnerabilityTickDamage(target, damage, attacker);
// //IFrame storage
//// int noDamageTicks = target.getNoDamageTicks();
//
//// String debug = "BLEED DMG RESULT: INC DMG:"+damage+", HP-Before:"+target.getHealth()+", HP-After:";
//
//// double incDmg = getFakeDamageFinalResult(attacker, target, DamageCause.ENTITY_ATTACK, damage);
//
//// double newHealth = Math.max(0, target.getHealth() - incDmg);
//
// //Don't kill things with a stone or wooden weapon
//// if(toolTier < 3 && newHealth == 0)
//// return;
//
// target.setMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY, mcMMO.metadataValue);
//
// if(newHealth == 0 && !(target instanceof Player))
// {
// target.damage(99999, attacker);
// }
// else
// {
//// Vector beforeRuptureVec = new Vector(target.getVelocity().getX(), target.getVelocity().getY(), target.getVelocity().getZ()); ;
// target.damage(damage, attacker);
//// debug+=target.getHealth();
// Bukkit.broadcastMessage(debug);
//// target.setNoDamageTicks(noDamageTicks); //Do not add additional IFrames
//// target.setVelocity(beforeRuptureVec);
// }
}
/**
@@ -741,14 +797,16 @@ public final class CombatUtils {
XPGainReason xpGainReason;
if (target instanceof Player defender) {
if (!ExperienceConfig.getInstance().getExperienceGainsPlayerVersusPlayerEnabled() ||
if (!ExperienceConfig.getInstance().getExperienceGainsPlayerVersusPlayerEnabled()
||
(mcMMO.p.getPartyConfig().isPartyEnabled() && mcMMO.p.getPartyManager().inSameParty(mcMMOPlayer.getPlayer(), (Player) target))) {
return;
}
xpGainReason = XPGainReason.PVP;
if (defender.isOnline() && SkillUtils.cooldownExpired(mcMMOPlayer.getRespawnATS(), Misc.PLAYER_RESPAWN_COOLDOWN_SECONDS)) {
if (defender.isOnline()
&& SkillUtils.cooldownExpired(mcMMOPlayer.getRespawnATS(), Misc.PLAYER_RESPAWN_COOLDOWN_SECONDS)) {
baseXP = 20 * ExperienceConfig.getInstance().getPlayerVersusPlayerXP();
}
}
@@ -807,7 +865,7 @@ public final class CombatUtils {
baseXP *= multiplier;
if (baseXP != 0) {
if (baseXP > 0) {
mcMMO.p.getFoliaLib().getImpl().runAtEntity(mcMMOPlayer.getPlayer(), new AwardCombatXpTask(mcMMOPlayer, primarySkillType, baseXP, target, xpGainReason));
}
}
@@ -945,31 +1003,12 @@ public final class CombatUtils {
}
}
/**
* Clean up metadata from a projectile
*
* @param entity projectile
*/
public static void cleanupArrowMetadata(@NotNull Projectile entity) {
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) {
entity.removeMetadata(MetadataConstants.METADATA_KEY_INF_ARROW, mcMMO.p);
}
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) {
entity.removeMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, mcMMO.p);
}
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) {
entity.removeMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, mcMMO.p);
}
}
/**
* Clean up metadata from a projectile after a minute has passed
*
* @param entity the projectile
* @param arrow the projectile
*/
public static void delayArrowMetaCleanup(@NotNull Projectile entity) {
mcMMO.p.getFoliaLib().getImpl().runLater(() -> cleanupArrowMetadata(entity), 20*60);
public static void delayArrowMetaCleanup(@NotNull Arrow arrow) {
mcMMO.p.getFoliaLib().getImpl().runLater(() -> ProjectileUtils.cleanupProjectileMetadata(arrow), 20*120);
}
}