From 2594dc1bca27548e560cf514aa9285e925d1a612 Mon Sep 17 00:00:00 2001 From: Robert Alan Chapton Date: Sat, 30 Mar 2024 06:09:59 -0700 Subject: [PATCH] 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. --- Changelog.txt | 69 +++- pom.xml | 9 +- .../com/gmail/nossr50/api/ExperienceAPI.java | 6 +- .../java/com/gmail/nossr50/api/PartyAPI.java | 2 - .../exceptions/ValueOutOfBoundsException.java | 9 + .../nossr50/chat/mailer/AdminChatMailer.java | 3 +- .../chat/message/PartyChatMessage.java | 4 +- .../nossr50/commands/CommandManager.java | 33 +- .../commands/chat/PartyChatCommand.java | 1 - .../commands/party/PartyAcceptCommand.java | 1 - .../party/PartyChangeOwnerCommand.java | 1 - .../commands/party/PartyCreateCommand.java | 1 - .../commands/party/PartyDisbandCommand.java | 1 - .../commands/party/PartyInfoCommand.java | 1 - .../commands/party/PartyInviteCommand.java | 1 - .../commands/party/PartyJoinCommand.java | 1 - .../commands/party/PartyKickCommand.java | 1 - .../commands/party/PartyQuitCommand.java | 1 - .../commands/party/PartyRenameCommand.java | 1 - .../alliance/PartyAllianceAcceptCommand.java | 1 - .../party/alliance/PartyAllianceCommand.java | 1 - .../alliance/PartyAllianceDisbandCommand.java | 1 - .../alliance/PartyAllianceInviteCommand.java | 1 - .../commands/party/teleport/PtpCommand.java | 1 - .../commands/skills/AcrobaticsCommand.java | 31 +- .../commands/skills/AlchemyCommand.java | 4 +- .../commands/skills/ArcheryCommand.java | 15 +- .../nossr50/commands/skills/AxesCommand.java | 14 +- .../commands/skills/CrossbowsCommand.java | 77 ++++ .../commands/skills/ExcavationCommand.java | 4 +- .../commands/skills/FishingCommand.java | 19 +- .../commands/skills/HerbalismCommand.java | 37 +- .../commands/skills/MiningCommand.java | 24 +- .../commands/skills/PowerLevelCommand.java | 40 ++ .../commands/skills/RepairCommand.java | 10 +- .../commands/skills/SalvageCommand.java | 5 +- .../nossr50/commands/skills/SkillCommand.java | 23 +- .../commands/skills/SmeltingCommand.java | 10 +- .../commands/skills/SwordsCommand.java | 13 +- .../commands/skills/TamingCommand.java | 20 +- .../commands/skills/TridentsCommand.java | 62 +++ .../commands/skills/UnarmedCommand.java | 18 +- .../commands/skills/WoodcuttingCommand.java | 37 +- .../gmail/nossr50/config/AdvancedConfig.java | 9 + .../com/gmail/nossr50/config/ChatConfig.java | 6 + .../gmail/nossr50/config/GeneralConfig.java | 2 + .../config/experience/ExperienceConfig.java | 127 +++---- .../database/DatabaseManagerFactory.java | 5 +- .../database/FlatFileDataProcessor.java | 7 + .../database/FlatFileDatabaseManager.java | 82 ++-- .../nossr50/database/SQLDatabaseManager.java | 357 +++++++++++------- .../database/flatfile/FlatFileDataUtil.java | 7 + .../gmail/nossr50/datatypes/party/Party.java | 1 - .../nossr50/datatypes/player/McMMOPlayer.java | 34 +- .../datatypes/player/PlayerProfile.java | 17 +- .../datatypes/skills/PrimarySkillType.java | 2 + .../datatypes/skills/SubSkillType.java | 19 +- .../datatypes/skills/SuperAbilityType.java | 92 +++-- .../nossr50/datatypes/skills/ToolType.java | 9 +- .../skills/subskills/acrobatics/Roll.java | 54 +-- .../nossr50/datatypes/treasure/Treasure.java | 42 ++- .../events/skills/McMMOPlayerSkillEvent.java | 6 +- .../SubSkillRandomCheckEvent.java | 88 ++--- .../nossr50/listeners/BlockListener.java | 2 +- .../nossr50/listeners/CommandListener.java | 40 -- .../nossr50/listeners/EntityListener.java | 90 ++--- .../nossr50/listeners/PlayerListener.java | 3 + src/main/java/com/gmail/nossr50/mcMMO.java | 5 +- .../com/gmail/nossr50/party/PartyManager.java | 3 - .../nossr50/runnables/SaveTimerTask.java | 1 - .../runnables/items/TeleportationWarmup.java | 1 - .../runnables/party/PartyAutoKickTask.java | 1 - .../runnables/skills/AbilityCooldownTask.java | 4 +- .../nossr50/runnables/skills/RuptureTask.java | 1 - .../skills/acrobatics/AcrobaticsManager.java | 6 +- .../skills/archery/ArcheryManager.java | 13 +- .../nossr50/skills/axes/AxesManager.java | 19 +- .../nossr50/skills/child/ChildConfig.java | 64 ---- .../nossr50/skills/child/FamilyTree.java | 54 --- .../nossr50/skills/crossbows/Crossbows.java | 41 ++ .../skills/crossbows/CrossbowsManager.java | 108 ++++++ .../skills/excavation/ExcavationManager.java | 45 ++- .../skills/fishing/FishingManager.java | 14 +- .../skills/herbalism/HerbalismManager.java | 14 +- .../nossr50/skills/mining/MiningManager.java | 36 +- .../nossr50/skills/repair/RepairManager.java | 10 +- .../skills/salvage/SalvageManager.java | 9 +- .../skills/smelting/SmeltingManager.java | 5 +- .../nossr50/skills/swords/SwordsManager.java | 9 +- .../nossr50/skills/taming/TamingManager.java | 29 +- .../skills/tridents/TridentsManager.java | 35 ++ .../skills/unarmed/UnarmedManager.java | 17 +- .../woodcutting/WoodcuttingManager.java | 63 +++- .../com/gmail/nossr50/util/BlockUtils.java | 37 +- .../com/gmail/nossr50/util/EventUtils.java | 5 +- .../com/gmail/nossr50/util/ItemUtils.java | 12 + .../gmail/nossr50/util/MaterialMapStore.java | 12 +- .../gmail/nossr50/util/MetadataConstants.java | 3 + .../java/com/gmail/nossr50/util/Misc.java | 22 ++ .../gmail/nossr50/util/MobHealthbarUtils.java | 2 +- .../com/gmail/nossr50/util/Permissions.java | 40 +- .../commands/CommandRegistrationManager.java | 6 + .../random/InvalidActivationException.java | 5 - .../nossr50/util/random/Probability.java | 65 ++++ .../nossr50/util/random/ProbabilityImpl.java | 62 +++ .../nossr50/util/random/ProbabilityUtil.java | 226 +++++++++++ .../util/random/RandomChanceExecution.java | 18 - .../util/random/RandomChanceSkill.java | 176 --------- .../util/random/RandomChanceSkillStatic.java | 61 --- .../util/random/RandomChanceStatic.java | 38 -- .../nossr50/util/random/RandomChanceUtil.java | 337 ----------------- .../util/random/SkillProbabilityType.java | 6 + .../util/scoreboards/ScoreboardWrapper.java | 3 +- .../nossr50/util/skills/CombatUtils.java | 179 +++++---- .../nossr50/util/skills/ProjectileUtils.java | 84 +++++ .../gmail/nossr50/util/skills/RankUtils.java | 5 +- .../gmail/nossr50/util/skills/SkillTools.java | 120 +++--- .../gmail/nossr50/util/skills/SkillUtils.java | 17 +- .../util/text/TextComponentFactory.java | 52 +-- src/main/resources/advanced.yml | 23 +- src/main/resources/chat.yml | 2 + src/main/resources/child.yml | 16 - src/main/resources/config.yml | 4 + src/main/resources/experience.yml | 14 +- .../resources/locale/locale_en_US.properties | 84 ++++- src/main/resources/plugin.yml | 212 ++++++++--- src/main/resources/skillranks.yml | 143 +++++++ .../com/gmail/nossr50/MMOTestEnvironment.java | 216 +++++++++++ .../database/FlatFileDatabaseManagerTest.java | 73 ++-- .../database/SQLDatabaseManagerTest.java | 245 ++++++++++++ .../gmail/nossr50/party/PartyManagerTest.java | 49 ++- .../skills/excavation/ExcavationTest.java | 120 ++++++ .../nossr50/skills/tridents/TridentsTest.java | 39 ++ .../skills/woodcutting/WoodcuttingTest.java | 108 ++++++ .../nossr50/util/random/ProbabilityTest.java | 105 ++++++ .../util/random/ProbabilityUtilTest.java | 66 ++++ .../nossr50/util/random/RandomChanceTest.java | 116 ------ .../nossr50/util/skills/SkillToolsTest.java | 16 - .../nossr50/util/text/TextUtilsTest.java | 2 +- src/test/resources/healthydb.users | 6 +- src/test/resources/olderdb.users | 3 + 141 files changed, 3586 insertions(+), 1991 deletions(-) create mode 100644 src/main/java/com/gmail/nossr50/api/exceptions/ValueOutOfBoundsException.java create mode 100644 src/main/java/com/gmail/nossr50/commands/skills/CrossbowsCommand.java create mode 100644 src/main/java/com/gmail/nossr50/commands/skills/PowerLevelCommand.java create mode 100644 src/main/java/com/gmail/nossr50/commands/skills/TridentsCommand.java delete mode 100644 src/main/java/com/gmail/nossr50/listeners/CommandListener.java delete mode 100644 src/main/java/com/gmail/nossr50/skills/child/ChildConfig.java delete mode 100644 src/main/java/com/gmail/nossr50/skills/child/FamilyTree.java create mode 100644 src/main/java/com/gmail/nossr50/skills/crossbows/Crossbows.java create mode 100644 src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowsManager.java create mode 100644 src/main/java/com/gmail/nossr50/skills/tridents/TridentsManager.java delete mode 100644 src/main/java/com/gmail/nossr50/util/random/InvalidActivationException.java create mode 100644 src/main/java/com/gmail/nossr50/util/random/Probability.java create mode 100644 src/main/java/com/gmail/nossr50/util/random/ProbabilityImpl.java create mode 100644 src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java delete mode 100644 src/main/java/com/gmail/nossr50/util/random/RandomChanceExecution.java delete mode 100644 src/main/java/com/gmail/nossr50/util/random/RandomChanceSkill.java delete mode 100644 src/main/java/com/gmail/nossr50/util/random/RandomChanceSkillStatic.java delete mode 100644 src/main/java/com/gmail/nossr50/util/random/RandomChanceStatic.java delete mode 100644 src/main/java/com/gmail/nossr50/util/random/RandomChanceUtil.java create mode 100644 src/main/java/com/gmail/nossr50/util/random/SkillProbabilityType.java create mode 100644 src/main/java/com/gmail/nossr50/util/skills/ProjectileUtils.java delete mode 100644 src/main/resources/child.yml create mode 100644 src/test/java/com/gmail/nossr50/MMOTestEnvironment.java create mode 100644 src/test/java/com/gmail/nossr50/database/SQLDatabaseManagerTest.java create mode 100644 src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java create mode 100644 src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java create mode 100644 src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java create mode 100644 src/test/java/com/gmail/nossr50/util/random/ProbabilityTest.java create mode 100644 src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java delete mode 100644 src/test/java/com/gmail/nossr50/util/random/RandomChanceTest.java delete mode 100644 src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java create mode 100644 src/test/resources/olderdb.users diff --git a/Changelog.txt b/Changelog.txt index f64a0f576..f5b1f81af 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -1,3 +1,54 @@ +Version 2.2.000 + 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 a 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 its 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. + Version 2.1.231 Fixed a bug preventing parties from being made without passwords (Thanks Momshroom) Updated korean locale (thanks mangchi57) @@ -1331,7 +1382,7 @@ Version 2.1.128 Fixed a bug where certain types of ore did not receive bonuses from Blast Mining Fixed a few locale errors with commands (API) Added ExperienceAPI::addCombatXP for adding combat XP to players, signature may change so its deprecated for now - mcMMO now logs whether or not its using FlatFile or SQL database on load + mcMMO now logs whether its using FlatFile or SQL database on load (1.16) Strider added to combat experience with a value of 1.2 NOTES: A more thorough look at Unarmed balance will happen in the future, the intention of this nerf is to make Unarmed less rewarding until it is leveled quite a bit. @@ -1351,7 +1402,7 @@ Version 2.1.127 Version 2.1.126 mcMMO now relies on NMS for some of its features, if NMS cannot properly be wired up when initializing mcMMO behaviours relying on NMS will either be partially supported or disabled mcMMO now has a compatibility mode, any features that require specific versions of Minecraft for full functionality will be disabled if your server is not running a compatible version, mcMMO will still function in compatibility mode, but either the feature will be modified or disabled depending on the version of the server software - New command /mmocompat - Shows information about whether or not mcMMO is fully functional or if some features are disabled due to the server software not being fully supported. Can be used by players or console. + New command /mmocompat - Shows information about whether mcMMO is fully functional or if some features are disabled due to the server software not being fully supported. Can be used by players or console. New command /mmoxpbar (alias /xpbarsettings) - Players can choose to always show XP bars or to never show XP bars on a per skill basis XPBars now last for 3 seconds before hiding instead of 2 seconds Fixed an exploit involving fishing rods @@ -1987,7 +2038,7 @@ Version 2.1.68 Fixed a bug where consuming food in the off hand did not trigger the Diet abilities Version 2.1.67 - The XP bar now reflects whether or not the player is receiving the early game boost + The XP bar now reflects whether the player is receiving the early game boost Players who are receiving an early game boost will be shown "Learning a skill..." as the title of the XP bar while gaining XP New locale string 'XPBar.Template.EarlyGameBoost' @@ -2036,7 +2087,7 @@ Version 2.1.63 Version 2.1.62 Added a new admin notification system, sensitive commands will print chat messages to "admins" (players with either Operator status or admin chat permission) Added a setting to disable the new admin notifications to config.yml 'General.AdminNotifications' (this will be more configurable in 2.2) - OPs and players with the admin chat permission will now see details about XP rate event commands regardless of whether or not the XP rate event messages are enabled + OPs and players with the admin chat permission will now see details about XP rate event commands regardless of whether the XP rate event messages are enabled Updated hu_HU locale (thanks andris155) Added XP for mining Magma_Block (default 30 XP - Update your config, see notes) Diamond tools & armor in the repair config now have a minimum level of 0 (Update your config, temporary hotfix, 2.2 addresses this issue, see notes) @@ -2044,9 +2095,9 @@ Version 2.1.62 New locale string - 'Server.ConsoleName' the name of the server console, this will be used in place of player names when sending admin notifications out if the command was used from console New locale string - 'Notifications.Admin.Format.Others' style formatting + prefix for admin notifications used in the other new strings below New locale string - 'Notifications.Admin.Format.Self' style formatting + prefix for admin command confirmations sent to the user who executed the command - New locale string - 'Notifications.Admin.XPRate.Start.Self' sent to the user who modifies the XP rate regardless of whether or not messages for the event are enabled + New locale string - 'Notifications.Admin.XPRate.Start.Self' sent to the user who modifies the XP rate regardless of whether messages for the event are enabled New locale string - 'Notifications.Admin.XPRate.Start.Others' details of who started an XP rate event are sent to players who have Operator status or admin chat permission when the command to start or modify XP of an event has been issued - New locale string - 'Notifications.Admin.XPRate.End.Self' sent to the user who ended the XP rate event regardless of whether or not messages for the event are enabled + New locale string - 'Notifications.Admin.XPRate.End.Self' sent to the user who ended the XP rate event regardless of whether messages for the event are enabled New locale string - 'Notifications.Admin.XPRate.End.Others' details of who ended an XP rate event are sent to players who have Operator status or admin chat permission when the command to end the event has been issued NOTES: @@ -2317,7 +2368,7 @@ Version 2.1.26 Notes: The new Limit Break subskills are intended to make Prot IV players less tanky and for you to feel more powerful for having high skill level. - Limit Break has 10 ranks, each rank gives 1 extra RAW damage, this is damage before reductions from armor and enchantments. The net result is you deal about 50% more damage with an end game skill compared to before. + Limit Break has 10 ranks, each rank gives 1 extra RAW damage, this is damage before reductions from armor and enchantments. The net result is you deal about 50% more damage with an endgame skill compared to before. With these new changes, most skills can 2 shot normal diamond armor, and it takes about 5 hits to kill someone in Prot IV Diamond Armor. I'm not sure everyone will like these changes, the net result is players are a lot easier to kill now, whereas before you could take quite a beating before getting killed. I collected several sets of data before making these changes, including damage to player with and without prot 4 diamond armor, damage to those players with and without enchanted weapons, damage with and without leveling your skills, and combinations of the previously mentioned things. @@ -4018,7 +4069,7 @@ Removed performance debugging Removed some useless settings from the config file Version 1.0.34 -Fixed the PVP setting determining whether or not you would hurt yourself from AoE Abilities +Fixed the PVP setting determining whether you would hurt yourself from AoE Abilities Added Dutch (nl) language support Super Breaker now gives the correct XP as determined by config.yml Sand Stone XP is now configurable and no longer shares the 'stone' node @@ -4028,7 +4079,7 @@ Version 1.0.33 Fixed the toggle for the Excavation drop 'Cocoa Beans' Fixed bug where Unarmed users could disarm without being bare handed Cocoa Beans now have an XP modifier in config.yml -You can now toggle whether or not Mobspawners will give XP (in config.yml) +You can now toggle whether Mobspawners will give XP (in config.yml) MySQL version now makes requests to the MySQL server less frequently (should help performance) Fixed bug with Skull Splitter hitting the user diff --git a/pom.xml b/pom.xml index 61f6317bb..2c5aea101 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 com.gmail.nossr50.mcMMO mcMMO - 2.1.231 + 2.2.000-RC1 mcMMO https://github.com/mcMMO-Dev/mcMMO @@ -258,6 +258,13 @@ + + + com.h2database + h2 + 2.2.224 + test + me.clip placeholderapi diff --git a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java index f18cd18ca..c626810ec 100644 --- a/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java +++ b/src/main/java/com/gmail/nossr50/api/ExperienceAPI.java @@ -9,7 +9,6 @@ import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.player.PlayerProfile; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.skills.child.FamilyTree; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.SkillTools; @@ -20,7 +19,6 @@ import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; -import java.util.Set; import java.util.UUID; public final class ExperienceAPI { @@ -706,7 +704,7 @@ public final class ExperienceAPI { PrimarySkillType skill = getSkillType(skillType); if (SkillTools.isChildSkill(skill)) { - Set parentSkills = FamilyTree.getParents(skill); + var parentSkills = mcMMO.p.getSkillTools().getChildSkillParents(skill); for (PrimarySkillType parentSkill : parentSkills) { profile.addLevels(parentSkill, (levels / parentSkills.size())); @@ -737,7 +735,7 @@ public final class ExperienceAPI { PrimarySkillType skill = getSkillType(skillType); if (SkillTools.isChildSkill(skill)) { - Set parentSkills = FamilyTree.getParents(skill); + var parentSkills = mcMMO.p.getSkillTools().getChildSkillParents(skill); for (PrimarySkillType parentSkill : parentSkills) { profile.addLevels(parentSkill, (levels / parentSkills.size())); diff --git a/src/main/java/com/gmail/nossr50/api/PartyAPI.java b/src/main/java/com/gmail/nossr50/api/PartyAPI.java index ce5efaf6e..e86bf4619 100644 --- a/src/main/java/com/gmail/nossr50/api/PartyAPI.java +++ b/src/main/java/com/gmail/nossr50/api/PartyAPI.java @@ -5,10 +5,8 @@ import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.party.PartyLeader; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; -import jdk.jfr.Experimental; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; diff --git a/src/main/java/com/gmail/nossr50/api/exceptions/ValueOutOfBoundsException.java b/src/main/java/com/gmail/nossr50/api/exceptions/ValueOutOfBoundsException.java new file mode 100644 index 000000000..4fc4a7a6f --- /dev/null +++ b/src/main/java/com/gmail/nossr50/api/exceptions/ValueOutOfBoundsException.java @@ -0,0 +1,9 @@ +package com.gmail.nossr50.api.exceptions; + +import org.jetbrains.annotations.NotNull; + +public class ValueOutOfBoundsException extends RuntimeException { + public ValueOutOfBoundsException(@NotNull String message) { + super(message); + } +} diff --git a/src/main/java/com/gmail/nossr50/chat/mailer/AdminChatMailer.java b/src/main/java/com/gmail/nossr50/chat/mailer/AdminChatMailer.java index f798f5d47..5ec0f5c37 100644 --- a/src/main/java/com/gmail/nossr50/chat/mailer/AdminChatMailer.java +++ b/src/main/java/com/gmail/nossr50/chat/mailer/AdminChatMailer.java @@ -3,6 +3,7 @@ package com.gmail.nossr50.chat.mailer; import com.gmail.nossr50.chat.author.Author; import com.gmail.nossr50.chat.message.AdminChatMessage; import com.gmail.nossr50.chat.message.ChatMessage; +import com.gmail.nossr50.config.ChatConfig; import com.gmail.nossr50.datatypes.chat.ChatChannel; import com.gmail.nossr50.events.chat.McMMOAdminChatEvent; import com.gmail.nossr50.events.chat.McMMOChatEvent; @@ -44,7 +45,7 @@ public class AdminChatMailer extends AbstractChatMailer { public @NotNull Predicate predicate() { return (commandSender) -> commandSender.isOp() || commandSender.hasPermission(MCMMO_CHAT_ADMINCHAT_PERMISSION) - || commandSender instanceof ConsoleCommandSender; + || (ChatConfig.getInstance().isConsoleIncludedInAudience(ChatChannel.ADMIN) && commandSender instanceof ConsoleCommandSender); } /** diff --git a/src/main/java/com/gmail/nossr50/chat/message/PartyChatMessage.java b/src/main/java/com/gmail/nossr50/chat/message/PartyChatMessage.java index 4c143ec69..756c3d457 100644 --- a/src/main/java/com/gmail/nossr50/chat/message/PartyChatMessage.java +++ b/src/main/java/com/gmail/nossr50/chat/message/PartyChatMessage.java @@ -1,6 +1,7 @@ package com.gmail.nossr50.chat.message; import com.gmail.nossr50.chat.author.Author; +import com.gmail.nossr50.config.ChatConfig; import com.gmail.nossr50.datatypes.chat.ChatChannel; import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; @@ -51,7 +52,8 @@ public class PartyChatMessage extends AbstractChatMessage { messagePartyChatSpies(spyMessage); //Console message - mcMMO.p.getChatManager().sendConsoleMessage(author, spyMessage); + if(ChatConfig.getInstance().isConsoleIncludedInAudience(ChatChannel.PARTY)) + mcMMO.p.getChatManager().sendConsoleMessage(author, spyMessage); } /** diff --git a/src/main/java/com/gmail/nossr50/commands/CommandManager.java b/src/main/java/com/gmail/nossr50/commands/CommandManager.java index c17c7fb1d..3b9d8516e 100644 --- a/src/main/java/com/gmail/nossr50/commands/CommandManager.java +++ b/src/main/java/com/gmail/nossr50/commands/CommandManager.java @@ -5,6 +5,7 @@ import co.aikar.commands.BukkitCommandManager; import co.aikar.commands.ConditionFailedException; import com.gmail.nossr50.commands.chat.AdminChatCommand; import com.gmail.nossr50.commands.chat.PartyChatCommand; +import com.gmail.nossr50.commands.skills.PowerLevelCommand; import com.gmail.nossr50.config.ChatConfig; import com.gmail.nossr50.datatypes.chat.ChatChannel; import com.gmail.nossr50.datatypes.player.McMMOPlayer; @@ -20,9 +21,14 @@ import org.jetbrains.annotations.NotNull; * For now this class will only handle ACF converted commands, all other commands will be handled elsewhere */ public class CommandManager { + public static final @NotNull String MMO_DATA_LOADED = "mmoDataLoaded"; + + //CHAT public static final @NotNull String ADMIN_CONDITION = "adminCondition"; public static final @NotNull String PARTY_CONDITION = "partyCondition"; - public static final @NotNull String MMO_DATA_LOADED = "mmoDataLoaded"; + + //SKILLS + public static final @NotNull String POWER_LEVEL_CONDITION = "powerLevelCondition"; private final @NotNull mcMMO pluginRef; private final @NotNull BukkitCommandManager bukkitCommandManager; @@ -36,9 +42,16 @@ public class CommandManager { } private void registerCommands() { + registerSkillCommands(); //TODO: Implement other skills not just power level registerChatCommands(); } + private void registerSkillCommands() { + if(mcMMO.p.getGeneralConfig().isMasterySystemEnabled()) { + bukkitCommandManager.registerCommand(new PowerLevelCommand(pluginRef)); + } + } + /** * Registers chat commands if the chat system is enabled */ @@ -54,6 +67,23 @@ public class CommandManager { } public void registerConditions() { + registerChatCommandConditions(); //Chat Commands + registerSkillConditions(); + } + + private void registerSkillConditions() { + bukkitCommandManager.getCommandConditions().addCondition(POWER_LEVEL_CONDITION, (context) -> { + BukkitCommandIssuer issuer = context.getIssuer(); + + if(issuer.getIssuer() instanceof Player) { + validateLoadedData(issuer.getPlayer()); + } else { + throw new ConditionFailedException(LocaleLoader.getString("Commands.NoConsole")); + } + }); + } + + private void registerChatCommandConditions() { // Method or Class based - Can only be used on methods bukkitCommandManager.getCommandConditions().addCondition(ADMIN_CONDITION, (context) -> { BukkitCommandIssuer issuer = context.getIssuer(); @@ -78,6 +108,7 @@ public class CommandManager { if(bukkitCommandIssuer.getIssuer() instanceof Player) { validateLoadedData(bukkitCommandIssuer.getPlayer()); validatePlayerParty(bukkitCommandIssuer.getPlayer()); + //TODO: Is there even a point in validating permission? look into this later validatePermission("mcmmo.chat.partychat", bukkitCommandIssuer.getPlayer()); } }); diff --git a/src/main/java/com/gmail/nossr50/commands/chat/PartyChatCommand.java b/src/main/java/com/gmail/nossr50/commands/chat/PartyChatCommand.java index acd7a6168..cfdbc61d8 100644 --- a/src/main/java/com/gmail/nossr50/commands/chat/PartyChatCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/chat/PartyChatCommand.java @@ -11,7 +11,6 @@ import com.gmail.nossr50.datatypes.chat.ChatChannel; import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.text.StringUtils; import org.bukkit.entity.Player; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyAcceptCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyAcceptCommand.java index 7febacd11..3b9d07f62 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyAcceptCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyAcceptCommand.java @@ -3,7 +3,6 @@ package com.gmail.nossr50.commands.party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyChangeOwnerCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyChangeOwnerCommand.java index 28d5079f7..29b3d64d1 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyChangeOwnerCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyChangeOwnerCommand.java @@ -3,7 +3,6 @@ package com.gmail.nossr50.commands.party; import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.OfflinePlayer; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyCreateCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyCreateCommand.java index 80d4d23ee..75a7c785c 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyCreateCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyCreateCommand.java @@ -3,7 +3,6 @@ package com.gmail.nossr50.commands.party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyDisbandCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyDisbandCommand.java index 3b0ed16bc..ac16d357b 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyDisbandCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyDisbandCommand.java @@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.events.party.McMMOPartyChangeEvent.EventReason; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyInfoCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyInfoCommand.java index c17707926..3e34078e8 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyInfoCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyInfoCommand.java @@ -6,7 +6,6 @@ import com.gmail.nossr50.datatypes.party.ShareMode; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.ChatColor; import org.bukkit.command.Command; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyInviteCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyInviteCommand.java index 65abe4e82..91d6b39be 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyInviteCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyInviteCommand.java @@ -4,7 +4,6 @@ import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyJoinCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyJoinCommand.java index be170cd4e..a5474d355 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyJoinCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyJoinCommand.java @@ -4,7 +4,6 @@ import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyKickCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyKickCommand.java index b9218b980..9ee2de089 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyKickCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyKickCommand.java @@ -4,7 +4,6 @@ import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.events.party.McMMOPartyChangeEvent.EventReason; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.OfflinePlayer; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyQuitCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyQuitCommand.java index 920f69ac9..880541add 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyQuitCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyQuitCommand.java @@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.events.party.McMMOPartyChangeEvent.EventReason; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/com/gmail/nossr50/commands/party/PartyRenameCommand.java b/src/main/java/com/gmail/nossr50/commands/party/PartyRenameCommand.java index c6e55fc4a..7d2954ad0 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/PartyRenameCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/PartyRenameCommand.java @@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.events.party.McMMOPartyChangeEvent.EventReason; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceAcceptCommand.java b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceAcceptCommand.java index 2e8eed2d5..b4bb002ab 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceAcceptCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceAcceptCommand.java @@ -3,7 +3,6 @@ package com.gmail.nossr50.commands.party.alliance; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceCommand.java b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceCommand.java index 5e47a451c..e39e7c3e2 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceCommand.java @@ -5,7 +5,6 @@ import com.gmail.nossr50.datatypes.party.PartyFeature; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; import com.google.common.collect.ImmutableList; diff --git a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceDisbandCommand.java b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceDisbandCommand.java index c8f2609e0..6b5e2dcea 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceDisbandCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceDisbandCommand.java @@ -4,7 +4,6 @@ import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; diff --git a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceInviteCommand.java b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceInviteCommand.java index 855a525e8..04d12b649 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceInviteCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/alliance/PartyAllianceInviteCommand.java @@ -4,7 +4,6 @@ import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.UserManager; import org.bukkit.command.Command; diff --git a/src/main/java/com/gmail/nossr50/commands/party/teleport/PtpCommand.java b/src/main/java/com/gmail/nossr50/commands/party/teleport/PtpCommand.java index e757688f8..10f0f1d98 100644 --- a/src/main/java/com/gmail/nossr50/commands/party/teleport/PtpCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/party/teleport/PtpCommand.java @@ -7,7 +7,6 @@ import com.gmail.nossr50.datatypes.party.PartyTeleportRecord; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.runnables.items.TeleportationWarmup; import com.gmail.nossr50.util.EventUtils; import com.gmail.nossr50.util.Misc; diff --git a/src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java index bb963168f..64950211c 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java @@ -5,9 +5,8 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill; import com.gmail.nossr50.listeners.InteractionManager; import com.gmail.nossr50.locale.LocaleLoader; -import com.gmail.nossr50.util.random.RandomChanceSkill; -import com.gmail.nossr50.util.random.RandomChanceUtil; -import com.gmail.nossr50.util.skills.SkillActivationType; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -30,7 +29,7 @@ public class AcrobaticsCommand extends SkillCommand { protected void dataCalculations(Player player, float skillValue) { // ACROBATICS_DODGE if (canDodge) { - String[] dodgeStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.ACROBATICS_DODGE); + String[] dodgeStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ACROBATICS_DODGE); dodgeChance = dodgeStrings[0]; dodgeChanceLucky = dodgeStrings[1]; } @@ -38,8 +37,8 @@ public class AcrobaticsCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canDodge = canUseSubskill(player, SubSkillType.ACROBATICS_DODGE); - canRoll = canUseSubskill(player, SubSkillType.ACROBATICS_ROLL); + canDodge = Permissions.canUseSubSkill(player, SubSkillType.ACROBATICS_DODGE); + canRoll = Permissions.canUseSubSkill(player, SubSkillType.ACROBATICS_ROLL); } @Override @@ -57,25 +56,7 @@ public class AcrobaticsCommand extends SkillCommand { if(abstractSubSkill != null) { - double rollChance, graceChance; - - //Chance to roll at half - RandomChanceSkill roll_rcs = new RandomChanceSkill(player, SubSkillType.ACROBATICS_ROLL); - - //Chance to graceful roll - RandomChanceSkill grace_rcs = new RandomChanceSkill(player, SubSkillType.ACROBATICS_ROLL); - grace_rcs.setSkillLevel(grace_rcs.getSkillLevel() * 2); //Double Odds - - //Chance Stat Calculations - rollChance = RandomChanceUtil.getRandomChanceExecutionChance(roll_rcs); - graceChance = RandomChanceUtil.getRandomChanceExecutionChance(grace_rcs); - //damageThreshold = mcMMO.p.getAdvancedConfig().getRollDamageThreshold(); - - String[] rollStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.ACROBATICS_ROLL); - - //Format - double rollChanceLucky = rollChance * 1.333D; - double graceChanceLucky = graceChance * 1.333D; + String[] rollStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ACROBATICS_ROLL); messages.add(getStatMessage(SubSkillType.ACROBATICS_ROLL, rollStrings[0]) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", rollStrings[1]) : "")); diff --git a/src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java index a8d5fcc8b..be34ebe2f 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java @@ -68,8 +68,8 @@ public class AlchemyCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canCatalysis = canUseSubskill(player, SubSkillType.ALCHEMY_CATALYSIS); - canConcoctions = canUseSubskill(player, SubSkillType.ALCHEMY_CONCOCTIONS); + canCatalysis = Permissions.canUseSubSkill(player, SubSkillType.ALCHEMY_CATALYSIS); + canConcoctions = Permissions.canUseSubSkill(player, SubSkillType.ALCHEMY_CONCOCTIONS); } @Override diff --git a/src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java index 59decaf9f..dd7bf8d4b 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java @@ -4,8 +4,9 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.skills.archery.Archery; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -32,14 +33,14 @@ public class ArcheryCommand extends SkillCommand { protected void dataCalculations(Player player, float skillValue) { // ARCHERY_ARROW_RETRIEVAL if (canRetrieve) { - String[] retrieveStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.ARCHERY_ARROW_RETRIEVAL); + String[] retrieveStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ARCHERY_ARROW_RETRIEVAL); retrieveChance = retrieveStrings[0]; retrieveChanceLucky = retrieveStrings[1]; } // ARCHERY_DAZE if (canDaze) { - String[] dazeStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.ARCHERY_DAZE); + String[] dazeStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ARCHERY_DAZE); dazeChance = dazeStrings[0]; dazeChanceLucky = dazeStrings[1]; } @@ -52,9 +53,9 @@ public class ArcheryCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canSkillShot = canUseSubskill(player, SubSkillType.ARCHERY_SKILL_SHOT); - canDaze = canUseSubskill(player, SubSkillType.ARCHERY_DAZE); - canRetrieve = canUseSubskill(player, SubSkillType.ARCHERY_ARROW_RETRIEVAL); + canSkillShot = Permissions.canUseSubSkill(player, SubSkillType.ARCHERY_SKILL_SHOT); + canDaze = Permissions.canUseSubSkill(player, SubSkillType.ARCHERY_DAZE); + canRetrieve = Permissions.canUseSubSkill(player, SubSkillType.ARCHERY_ARROW_RETRIEVAL); } @Override @@ -75,7 +76,7 @@ public class ArcheryCommand extends SkillCommand { messages.add(getStatMessage(SubSkillType.ARCHERY_SKILL_SHOT, skillShotBonus)); } - if(canUseSubskill(player, SubSkillType.ARCHERY_ARCHERY_LIMIT_BREAK)) { + if(Permissions.canUseSubSkill(player, SubSkillType.ARCHERY_ARCHERY_LIMIT_BREAK)) { messages.add(getStatMessage(SubSkillType.ARCHERY_ARCHERY_LIMIT_BREAK, String.valueOf(CombatUtils.getLimitBreakDamageAgainstQuality(player, SubSkillType.ARCHERY_ARCHERY_LIMIT_BREAK, 1000)))); } diff --git a/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java index d6b5c029a..49346aca2 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java @@ -6,9 +6,9 @@ import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.skills.axes.Axes; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -48,7 +48,7 @@ public class AxesCommand extends SkillCommand { // CRITICAL HIT if (canCritical) { - String[] criticalHitStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.AXES_CRITICAL_STRIKES); + String[] criticalHitStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.AXES_CRITICAL_STRIKES); critChance = criticalHitStrings[0]; critChanceLucky = criticalHitStrings[1]; } @@ -64,10 +64,10 @@ public class AxesCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { canSkullSplitter = Permissions.skullSplitter(player) && RankUtils.hasUnlockedSubskill(player, SubSkillType.AXES_SKULL_SPLITTER); - canCritical = canUseSubskill(player, SubSkillType.AXES_CRITICAL_STRIKES); - canAxeMastery = canUseSubskill(player, SubSkillType.AXES_AXE_MASTERY); - canImpact = canUseSubskill(player, SubSkillType.AXES_ARMOR_IMPACT); - canGreaterImpact = canUseSubskill(player, SubSkillType.AXES_GREATER_IMPACT); + canCritical = Permissions.canUseSubSkill(player, SubSkillType.AXES_CRITICAL_STRIKES); + canAxeMastery = Permissions.canUseSubSkill(player, SubSkillType.AXES_AXE_MASTERY); + canImpact = Permissions.canUseSubSkill(player, SubSkillType.AXES_ARMOR_IMPACT); + canGreaterImpact = Permissions.canUseSubSkill(player, SubSkillType.AXES_GREATER_IMPACT); } @Override @@ -96,7 +96,7 @@ public class AxesCommand extends SkillCommand { + (hasEndurance ? LocaleLoader.getString("Perks.ActivationTime.Bonus", skullSplitterLengthEndurance) : "")); } - if(canUseSubskill(player, SubSkillType.AXES_AXES_LIMIT_BREAK)) { + if(Permissions.canUseSubSkill(player, SubSkillType.AXES_AXES_LIMIT_BREAK)) { messages.add(getStatMessage(SubSkillType.AXES_AXES_LIMIT_BREAK, String.valueOf(CombatUtils.getLimitBreakDamageAgainstQuality(player, SubSkillType.AXES_AXES_LIMIT_BREAK, 1000)))); } diff --git a/src/main/java/com/gmail/nossr50/commands/skills/CrossbowsCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/CrossbowsCommand.java new file mode 100644 index 000000000..6ef04eaae --- /dev/null +++ b/src/main/java/com/gmail/nossr50/commands/skills/CrossbowsCommand.java @@ -0,0 +1,77 @@ +package com.gmail.nossr50.commands.skills; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.skills.CombatUtils; +import com.gmail.nossr50.util.skills.RankUtils; +import com.gmail.nossr50.util.text.TextComponentFactory; +import net.kyori.adventure.text.Component; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +import static com.gmail.nossr50.datatypes.skills.SubSkillType.*; + +public class CrossbowsCommand extends SkillCommand { + private boolean canTrickShot; + private boolean canPoweredShot; + + public CrossbowsCommand() { + super(PrimarySkillType.CROSSBOWS); + } + + @Override + protected void dataCalculations(Player player, float skillValue) { + // TODO: Implement data calculations + } + + @Override + protected void permissionsCheck(Player player) { + canTrickShot = RankUtils.hasUnlockedSubskill(player, CROSSBOWS_TRICK_SHOT) + && Permissions.trickShot(player); + + canPoweredShot = RankUtils.hasUnlockedSubskill(player, CROSSBOWS_POWERED_SHOT) + && Permissions.poweredShot(player); + } + + @Override + protected List statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) { + List messages = new ArrayList<>(); + + McMMOPlayer mmoPlayer = UserManager.getPlayer(player); + if (mmoPlayer == null) { + return messages; + } + + if (canPoweredShot) { + messages.add(getStatMessage(ARCHERY_SKILL_SHOT, percent.format(mmoPlayer.getCrossbowsManager().getDamageBonusPercent(player)))); + } + + if (canTrickShot) { + messages.add(getStatMessage(CROSSBOWS_TRICK_SHOT, + String.valueOf(mmoPlayer.getCrossbowsManager().getTrickShotMaxBounceCount()))); + } + + if(Permissions.canUseSubSkill(player, CROSSBOWS_CROSSBOWS_LIMIT_BREAK)) { + messages.add(getStatMessage(CROSSBOWS_CROSSBOWS_LIMIT_BREAK, + String.valueOf(CombatUtils.getLimitBreakDamageAgainstQuality(player, CROSSBOWS_CROSSBOWS_LIMIT_BREAK, 1000)))); + } + + messages.add(ChatColor.GRAY + "The Crossbows skill is a work in progress and is still being developed, feedback would be appreciated in the mcMMO discord server."); + + return messages; + } + + @Override + protected List getTextComponents(Player player) { + List textComponents = new ArrayList<>(); + + TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkillType.CROSSBOWS); + + return textComponents; + } +} diff --git a/src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java index de7341e93..952766b15 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java @@ -38,7 +38,7 @@ public class ExcavationCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { canGigaDrill = Permissions.gigaDrillBreaker(player) && RankUtils.hasUnlockedSubskill(player, SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER); - canTreasureHunt = canUseSubskill(player, SubSkillType.EXCAVATION_ARCHAEOLOGY); + canTreasureHunt = Permissions.canUseSubSkill(player, SubSkillType.EXCAVATION_ARCHAEOLOGY); } @Override @@ -54,7 +54,7 @@ public class ExcavationCommand extends SkillCommand { //messages.add(LocaleLoader.getString("Excavation.Effect.Length", gigaDrillBreakerLength) + (hasEndurance ? LocaleLoader.getString("Perks.ActivationTime.Bonus", gigaDrillBreakerLengthEndurance) : "")); } - if(canUseSubskill(player, SubSkillType.EXCAVATION_ARCHAEOLOGY)) { + if(Permissions.canUseSubSkill(player, SubSkillType.EXCAVATION_ARCHAEOLOGY)) { messages.add(getStatMessage(false, false, SubSkillType.EXCAVATION_ARCHAEOLOGY, percent.format(excavationManager.getArchaelogyExperienceOrbChance() / 100.0D))); messages.add(getStatMessage(true, false, SubSkillType.EXCAVATION_ARCHAEOLOGY, diff --git a/src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java index f22f8399e..6b188d2d7 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java @@ -7,8 +7,10 @@ import com.gmail.nossr50.datatypes.treasure.Rarity; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.skills.fishing.FishingManager; +import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.Probability; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.text.StringUtils; import com.gmail.nossr50.util.text.TextComponentFactory; @@ -79,7 +81,8 @@ public class FishingCommand extends SkillCommand { // FISHING_SHAKE if (canShake) { - String[] shakeStrings = RandomChanceUtil.calculateAbilityDisplayValuesStatic(player, PrimarySkillType.FISHING, fishingManager.getShakeChance()); + Probability shakeProbability = Probability.ofPercent(fishingManager.getShakeChance()); + String[] shakeStrings = ProbabilityUtil.getRNGDisplayValues(shakeProbability); shakeChance = shakeStrings[0]; shakeChanceLucky = shakeStrings[1]; } @@ -98,12 +101,12 @@ public class FishingCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canTreasureHunt = canUseSubskill(player, SubSkillType.FISHING_TREASURE_HUNTER); - canMagicHunt = canUseSubskill(player, SubSkillType.FISHING_MAGIC_HUNTER) && canUseSubskill(player, SubSkillType.FISHING_TREASURE_HUNTER); - canShake = canUseSubskill(player, SubSkillType.FISHING_SHAKE); - canFishermansDiet = canUseSubskill(player, SubSkillType.FISHING_FISHERMANS_DIET); - canMasterAngler = mcMMO.getCompatibilityManager().getMasterAnglerCompatibilityLayer() != null && canUseSubskill(player, SubSkillType.FISHING_MASTER_ANGLER); - canIceFish = canUseSubskill(player, SubSkillType.FISHING_ICE_FISHING); + canTreasureHunt = Permissions.canUseSubSkill(player, SubSkillType.FISHING_TREASURE_HUNTER); + canMagicHunt = Permissions.canUseSubSkill(player, SubSkillType.FISHING_MAGIC_HUNTER) && Permissions.canUseSubSkill(player, SubSkillType.FISHING_TREASURE_HUNTER); + canShake = Permissions.canUseSubSkill(player, SubSkillType.FISHING_SHAKE); + canFishermansDiet = Permissions.canUseSubSkill(player, SubSkillType.FISHING_FISHERMANS_DIET); + canMasterAngler = mcMMO.getCompatibilityManager().getMasterAnglerCompatibilityLayer() != null && Permissions.canUseSubSkill(player, SubSkillType.FISHING_MASTER_ANGLER); + canIceFish = Permissions.canUseSubSkill(player, SubSkillType.FISHING_ICE_FISHING); } @Override diff --git a/src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java index eba4520a7..8a5ce8d33 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java @@ -5,8 +5,8 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.Material; @@ -24,6 +24,8 @@ public class HerbalismCommand extends SkillCommand { private int farmersDietRank; private String doubleDropChance; private String doubleDropChanceLucky; + private String tripleDropChance; + private String tripleDropChanceLucky; private String hylianLuckChance; private String hylianLuckChanceLucky; private String shroomThumbChance; @@ -35,6 +37,7 @@ public class HerbalismCommand extends SkillCommand { private boolean canGreenThumbBlocks; private boolean canFarmersDiet; private boolean canDoubleDrop; + private boolean canTripleDrop; private boolean canShroomThumb; public HerbalismCommand() { @@ -46,10 +49,16 @@ public class HerbalismCommand extends SkillCommand { // DOUBLE DROPS if (canDoubleDrop) { - String[] doubleDropStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.HERBALISM_DOUBLE_DROPS); + String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_DOUBLE_DROPS); doubleDropChance = doubleDropStrings[0]; doubleDropChanceLucky = doubleDropStrings[1]; } + + if (canTripleDrop) { + String[] tripleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_VERDANT_BOUNTY); + tripleDropChance = tripleDropStrings[0]; + tripleDropChanceLucky = tripleDropStrings[1]; + } // FARMERS DIET if (canFarmersDiet) { @@ -67,21 +76,21 @@ public class HerbalismCommand extends SkillCommand { if (canGreenThumbBlocks || canGreenThumbPlants) { greenThumbStage = RankUtils.getRank(player, SubSkillType.HERBALISM_GREEN_THUMB); - String[] greenThumbStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.HERBALISM_GREEN_THUMB); + String[] greenThumbStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_GREEN_THUMB); greenThumbChance = greenThumbStrings[0]; greenThumbChanceLucky = greenThumbStrings[1]; } // HYLIAN LUCK if (hasHylianLuck) { - String[] hylianLuckStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.HERBALISM_HYLIAN_LUCK); + String[] hylianLuckStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_HYLIAN_LUCK); hylianLuckChance = hylianLuckStrings[0]; hylianLuckChanceLucky = hylianLuckStrings[1]; } // SHROOM THUMB if (canShroomThumb) { - String[] shroomThumbStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.HERBALISM_SHROOM_THUMB); + String[] shroomThumbStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.HERBALISM_SHROOM_THUMB); shroomThumbChance = shroomThumbStrings[0]; shroomThumbChanceLucky = shroomThumbStrings[1]; } @@ -89,13 +98,14 @@ public class HerbalismCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - hasHylianLuck = canUseSubskill(player, SubSkillType.HERBALISM_HYLIAN_LUCK); + hasHylianLuck = Permissions.canUseSubSkill(player, SubSkillType.HERBALISM_HYLIAN_LUCK); canGreenTerra = Permissions.greenTerra(player); canGreenThumbPlants = RankUtils.hasUnlockedSubskill(player, SubSkillType.HERBALISM_GREEN_THUMB) && (Permissions.greenThumbPlant(player, Material.WHEAT) || Permissions.greenThumbPlant(player, Material.CARROT) || Permissions.greenThumbPlant(player, Material.POTATO) || Permissions.greenThumbPlant(player, Material.BEETROOTS) || Permissions.greenThumbPlant(player, Material.NETHER_WART) || Permissions.greenThumbPlant(player, Material.COCOA)); canGreenThumbBlocks = RankUtils.hasUnlockedSubskill(player, SubSkillType.HERBALISM_GREEN_THUMB) && (Permissions.greenThumbBlock(player, Material.DIRT) || Permissions.greenThumbBlock(player, Material.COBBLESTONE) || Permissions.greenThumbBlock(player, Material.COBBLESTONE_WALL) || Permissions.greenThumbBlock(player, Material.STONE_BRICKS)); - canFarmersDiet = canUseSubskill(player, SubSkillType.HERBALISM_FARMERS_DIET); - canDoubleDrop = canUseSubskill(player, SubSkillType.HERBALISM_DOUBLE_DROPS) && !mcMMO.p.getGeneralConfig().getDoubleDropsDisabled(skill); - canShroomThumb = canUseSubskill(player, SubSkillType.HERBALISM_SHROOM_THUMB); + canFarmersDiet = Permissions.canUseSubSkill(player, SubSkillType.HERBALISM_FARMERS_DIET); + canDoubleDrop = Permissions.canUseSubSkill(player, SubSkillType.HERBALISM_DOUBLE_DROPS) && !mcMMO.p.getGeneralConfig().getDoubleDropsDisabled(skill); + canTripleDrop = Permissions.canUseSubSkill(player, SubSkillType.HERBALISM_VERDANT_BOUNTY) && !mcMMO.p.getGeneralConfig().getDoubleDropsDisabled(skill); + canShroomThumb = Permissions.canUseSubSkill(player, SubSkillType.HERBALISM_SHROOM_THUMB); } @Override @@ -106,11 +116,16 @@ public class HerbalismCommand extends SkillCommand { messages.add(getStatMessage(SubSkillType.HERBALISM_DOUBLE_DROPS, doubleDropChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", doubleDropChanceLucky) : "")); } - + + if (canTripleDrop) { + messages.add(getStatMessage(SubSkillType.HERBALISM_VERDANT_BOUNTY, tripleDropChance) + + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", tripleDropChanceLucky) : "")); + } + if (canFarmersDiet) { messages.add(getStatMessage(false, true, SubSkillType.HERBALISM_FARMERS_DIET, String.valueOf(farmersDietRank))); } - + if (canGreenTerra) { messages.add(getStatMessage(SubSkillType.HERBALISM_GREEN_TERRA, greenTerraLength) + (hasEndurance ? LocaleLoader.getString("Perks.ActivationTime.Bonus", greenTerraLengthEndurance) : "")); diff --git a/src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java index 5e7a14a7a..b756dd282 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java @@ -6,8 +6,8 @@ import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.skills.mining.MiningManager; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -18,6 +18,8 @@ import java.util.List; public class MiningCommand extends SkillCommand { private String doubleDropChance; private String doubleDropChanceLucky; + private String tripleDropChance; + private String tripleDropChanceLucky; private String superBreakerLength; private String superBreakerLengthEndurance; @@ -30,6 +32,7 @@ public class MiningCommand extends SkillCommand { private boolean canSuperBreaker; private boolean canDoubleDrop; + private boolean canTripleDrop; private boolean canBlast; private boolean canBiggerBombs; private boolean canDemoExpert; @@ -51,10 +54,17 @@ public class MiningCommand extends SkillCommand { blastDamageDecrease = percent.format(miningManager.getBlastDamageModifier() / 100.0D); blastRadiusIncrease = miningManager.getBlastRadiusModifier(); } + + // Mastery TRIPLE DROPS + if (canTripleDrop) { + String[] masteryTripleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.MINING_MOTHER_LODE); + tripleDropChance = masteryTripleDropStrings[0]; + tripleDropChanceLucky = masteryTripleDropStrings[1]; + } // DOUBLE DROPS if (canDoubleDrop) { - String[] doubleDropStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.MINING_DOUBLE_DROPS); + String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.MINING_DOUBLE_DROPS); doubleDropChance = doubleDropStrings[0]; doubleDropChanceLucky = doubleDropStrings[1]; } @@ -72,7 +82,8 @@ public class MiningCommand extends SkillCommand { canBiggerBombs = RankUtils.hasUnlockedSubskill(player, SubSkillType.MINING_BIGGER_BOMBS) && Permissions.biggerBombs(player); canBlast = RankUtils.hasUnlockedSubskill(player, SubSkillType.MINING_BLAST_MINING) && Permissions.remoteDetonation(player); canDemoExpert = RankUtils.hasUnlockedSubskill(player, SubSkillType.MINING_DEMOLITIONS_EXPERTISE) && Permissions.demolitionsExpertise(player); - canDoubleDrop = canUseSubskill(player, SubSkillType.MINING_DOUBLE_DROPS); + canDoubleDrop = Permissions.canUseSubSkill(player, SubSkillType.MINING_DOUBLE_DROPS); + canTripleDrop = Permissions.canUseSubSkill(player, SubSkillType.MINING_MOTHER_LODE); canSuperBreaker = RankUtils.hasUnlockedSubskill(player, SubSkillType.MINING_SUPER_BREAKER) && Permissions.superBreaker(player); } @@ -94,13 +105,18 @@ public class MiningCommand extends SkillCommand { messages.add(getStatMessage(SubSkillType.MINING_DEMOLITIONS_EXPERTISE, blastDamageDecrease)); //messages.add(LocaleLoader.getString("Mining.Effect.Decrease", blastDamageDecrease)); } - + if (canDoubleDrop) { messages.add(getStatMessage(SubSkillType.MINING_DOUBLE_DROPS, doubleDropChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", doubleDropChanceLucky) : "")); //messages.add(LocaleLoader.getString("Mining.Effect.DropChance", doubleDropChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", doubleDropChanceLucky) : "")); } + if(canTripleDrop) { + messages.add(getStatMessage(SubSkillType.MINING_MOTHER_LODE, tripleDropChance) + + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", tripleDropChanceLucky) : "")); + } + if (canSuperBreaker) { messages.add(getStatMessage(SubSkillType.MINING_SUPER_BREAKER, superBreakerLength) + (hasEndurance ? LocaleLoader.getString("Perks.ActivationTime.Bonus", superBreakerLengthEndurance) : "")); diff --git a/src/main/java/com/gmail/nossr50/commands/skills/PowerLevelCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/PowerLevelCommand.java new file mode 100644 index 000000000..639275b55 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/commands/skills/PowerLevelCommand.java @@ -0,0 +1,40 @@ +package com.gmail.nossr50.commands.skills; + +import co.aikar.commands.BaseCommand; +import co.aikar.commands.BukkitCommandIssuer; +import co.aikar.commands.annotation.CommandAlias; +import co.aikar.commands.annotation.CommandPermission; +import co.aikar.commands.annotation.Conditions; +import co.aikar.commands.annotation.Default; +import com.gmail.nossr50.commands.CommandManager; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.player.UserManager; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +@CommandPermission("mcmmo.commands.mmopower") +@CommandAlias("mmopower|mmopowerlevel|powerlevel") +public class PowerLevelCommand extends BaseCommand { + private final @NotNull mcMMO pluginRef; + + public PowerLevelCommand(@NotNull mcMMO pluginRef) { + this.pluginRef = pluginRef; + } + + @Default + @Conditions(CommandManager.POWER_LEVEL_CONDITION) + public void processCommand(String[] args) { + BukkitCommandIssuer bukkitCommandIssuer = (BukkitCommandIssuer) getCurrentCommandIssuer(); + Player player = bukkitCommandIssuer.getPlayer(); + McMMOPlayer mmoPlayer = UserManager.getPlayer(player); //Should never be null at this point because its caught in an ACF validation + if (mmoPlayer == null) { + return; + } + + int powerLevel = mmoPlayer.getPowerLevel(); + + mmoPlayer.getPlayer().sendMessage(ChatColor.DARK_AQUA + "Your " + ChatColor.GOLD + "[mcMMO]" + ChatColor.DARK_AQUA + " power level is: " + ChatColor.GREEN + powerLevel); + } +} diff --git a/src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java index 1941357d0..32927ddcf 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java @@ -11,8 +11,8 @@ import com.gmail.nossr50.skills.repair.RepairManager; import com.gmail.nossr50.skills.repair.repairables.Repairable; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.Material; @@ -68,7 +68,7 @@ public class RepairCommand extends SkillCommand { // SUPER REPAIR if (canSuperRepair) { - String[] superRepairStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.REPAIR_SUPER_REPAIR); + String[] superRepairStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.REPAIR_SUPER_REPAIR); superRepairChance = superRepairStrings[0]; superRepairChanceLucky = superRepairStrings[1]; } @@ -76,9 +76,9 @@ public class RepairCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canSuperRepair = canUseSubskill(player, SubSkillType.REPAIR_SUPER_REPAIR); - canMasterRepair = canUseSubskill(player, SubSkillType.REPAIR_REPAIR_MASTERY); - canArcaneForge = canUseSubskill(player, SubSkillType.REPAIR_ARCANE_FORGING); + canSuperRepair = Permissions.canUseSubSkill(player, SubSkillType.REPAIR_SUPER_REPAIR); + canMasterRepair = Permissions.canUseSubSkill(player, SubSkillType.REPAIR_REPAIR_MASTERY); + canArcaneForge = Permissions.canUseSubSkill(player, SubSkillType.REPAIR_ARCANE_FORGING); canRepairDiamond = Permissions.repairMaterialType(player, MaterialType.DIAMOND); canRepairGold = Permissions.repairMaterialType(player, MaterialType.GOLD); canRepairIron = Permissions.repairMaterialType(player, MaterialType.IRON); diff --git a/src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java index 770299951..93a748023 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java @@ -5,6 +5,7 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.skills.salvage.Salvage; import com.gmail.nossr50.skills.salvage.SalvageManager; +import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.text.TextComponentFactory; @@ -30,8 +31,8 @@ public class SalvageCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canScrapCollector = canUseSubskill(player, SubSkillType.SALVAGE_SCRAP_COLLECTOR); - canArcaneSalvage = canUseSubskill(player, SubSkillType.SALVAGE_ARCANE_SALVAGE); + canScrapCollector = Permissions.canUseSubSkill(player, SubSkillType.SALVAGE_SCRAP_COLLECTOR); + canArcaneSalvage = Permissions.canUseSubSkill(player, SubSkillType.SALVAGE_ARCANE_SALVAGE); } @Override diff --git a/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java index df3f8a8a4..61b2f7ae4 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java @@ -5,16 +5,12 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.skills.child.FamilyTree; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.commands.CommandUtils; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; import com.gmail.nossr50.util.scoreboards.ScoreboardManager; import com.gmail.nossr50.util.skills.PerksUtils; -import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.skills.SkillTools; import com.gmail.nossr50.util.text.StringUtils; import com.gmail.nossr50.util.text.TextComponentFactory; @@ -32,7 +28,6 @@ import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Locale; -import java.util.Set; public abstract class SkillCommand implements TabExecutor { protected PrimarySkillType skill; @@ -173,7 +168,7 @@ public abstract class SkillCommand implements TabExecutor { */ - Set parents = FamilyTree.getParents(skill); + var parents = mcMMO.p.getSkillTools().getChildSkillParents(skill); //TODO: Add JSON here /*player.sendMessage(parent.getName() + " - " + LocaleLoader.getString("Effects.Level.Overhaul", mcMMOPlayer.getSkillLevel(parent), mcMMOPlayer.getSkillXpLevel(parent), mcMMOPlayer.getXpToLevel(parent)))*/ @@ -232,9 +227,9 @@ public abstract class SkillCommand implements TabExecutor { return Math.min((int) skillValue, maxLevel) / rankChangeLevel; } - protected String[] getAbilityDisplayValues(SkillActivationType skillActivationType, Player player, SubSkillType subSkill) { - return RandomChanceUtil.calculateAbilityDisplayValues(skillActivationType, player, subSkill); - } +// protected String[] getAbilityDisplayValues(SkillActivationType skillActivationType, Player player, SubSkillType subSkill) { +// return RandomChanceUtil.calculateAbilityDisplayValues(skillActivationType, player, subSkill); +// } protected String[] calculateLengthDisplayValues(Player player, float skillValue) { int maxLength = mcMMO.p.getSkillTools().getSuperAbilityMaxLength(mcMMO.p.getSkillTools().getSuperAbility(skill)); @@ -297,14 +292,4 @@ public abstract class SkillCommand implements TabExecutor { protected abstract List getTextComponents(Player player); - /** - * Checks if a player can use a skill - * @param player target player - * @param subSkillType target subskill - * @return true if the player has permission and has the skill unlocked - */ - protected boolean canUseSubskill(Player player, SubSkillType subSkillType) - { - return Permissions.isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType); - } } diff --git a/src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java index 55544acc2..297662e10 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java @@ -5,8 +5,8 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -39,14 +39,14 @@ public class SmeltingCommand extends SkillCommand { // FLUX MINING /*if (canFluxMine) { - String[] fluxMiningStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.SMELTING_FLUX_MINING); + String[] fluxMiningStrings = getRNGDisplayValues(player, SubSkillType.SMELTING_FLUX_MINING); str_fluxMiningChance = fluxMiningStrings[0]; str_fluxMiningChanceLucky = fluxMiningStrings[1]; }*/ // SECOND SMELT if (canSecondSmelt) { - String[] secondSmeltStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.SMELTING_SECOND_SMELT); + String[] secondSmeltStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.SMELTING_SECOND_SMELT); str_secondSmeltChance = secondSmeltStrings[0]; str_secondSmeltChanceLucky = secondSmeltStrings[1]; } @@ -54,8 +54,8 @@ public class SmeltingCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canFuelEfficiency = canUseSubskill(player, SubSkillType.SMELTING_FUEL_EFFICIENCY); - canSecondSmelt = canUseSubskill(player, SubSkillType.SMELTING_SECOND_SMELT); + canFuelEfficiency = Permissions.canUseSubSkill(player, SubSkillType.SMELTING_FUEL_EFFICIENCY); + canSecondSmelt = Permissions.canUseSubSkill(player, SubSkillType.SMELTING_SECOND_SMELT); //canFluxMine = canUseSubskill(player, SubSkillType.SMELTING_FLUX_MINING); canUnderstandTheArt = Permissions.vanillaXpBoost(player, skill) && RankUtils.hasUnlockedSubskill(player, SubSkillType.SMELTING_UNDERSTANDING_THE_ART); } diff --git a/src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java index 954c39024..03d15d4ce 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java @@ -6,9 +6,10 @@ import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; +import com.gmail.nossr50.util.skills.SkillUtils; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -38,7 +39,7 @@ public class SwordsCommand extends SkillCommand { protected void dataCalculations(Player player, float skillValue) { // SWORDS_COUNTER_ATTACK if (canCounter) { - String[] counterStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.SWORDS_COUNTER_ATTACK); + String[] counterStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.SWORDS_COUNTER_ATTACK); counterChance = counterStrings[0]; counterChanceLucky = counterStrings[1]; } @@ -69,8 +70,8 @@ public class SwordsCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canRupture = canUseSubskill(player, SubSkillType.SWORDS_RUPTURE); - canCounter = canUseSubskill(player, SubSkillType.SWORDS_COUNTER_ATTACK); + canRupture = SkillUtils.canUseSubskill(player, SubSkillType.SWORDS_RUPTURE); + canCounter = SkillUtils.canUseSubskill(player, SubSkillType.SWORDS_COUNTER_ATTACK); canSerratedStrike = RankUtils.hasUnlockedSubskill(player, SubSkillType.SWORDS_SERRATED_STRIKES) && Permissions.serratedStrikes(player); } @@ -101,13 +102,13 @@ public class SwordsCommand extends SkillCommand { + (hasEndurance ? LocaleLoader.getString("Perks.ActivationTime.Bonus", serratedStrikesLengthEndurance) : "")); } - if(canUseSubskill(player, SubSkillType.SWORDS_STAB)) + if(SkillUtils.canUseSubskill(player, SubSkillType.SWORDS_STAB)) { messages.add(getStatMessage(SubSkillType.SWORDS_STAB, String.valueOf(UserManager.getPlayer(player).getSwordsManager().getStabDamage()))); } - if(canUseSubskill(player, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK)) { + if(SkillUtils.canUseSubskill(player, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK)) { messages.add(getStatMessage(SubSkillType.SWORDS_SWORDS_LIMIT_BREAK, String.valueOf(CombatUtils.getLimitBreakDamageAgainstQuality(player, SubSkillType.SWORDS_SWORDS_LIMIT_BREAK, 1000)))); } diff --git a/src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java index 35f04f5bc..fc6fd2e46 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java @@ -5,7 +5,7 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.skills.taming.Taming; import com.gmail.nossr50.util.Permissions; -import com.gmail.nossr50.util.skills.SkillActivationType; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.EntityType; @@ -35,7 +35,7 @@ public class TamingCommand extends SkillCommand { @Override protected void dataCalculations(Player player, float skillValue) { if (canGore) { - String[] goreStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.TAMING_GORE); + String[] goreStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.TAMING_GORE); goreChance = goreStrings[0]; goreChanceLucky = goreStrings[1]; } @@ -43,15 +43,15 @@ public class TamingCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { - canBeastLore = canUseSubskill(player, SubSkillType.TAMING_BEAST_LORE); + canBeastLore = Permissions.canUseSubSkill(player, SubSkillType.TAMING_BEAST_LORE); canCallWild = Permissions.callOfTheWild(player, EntityType.HORSE) || Permissions.callOfTheWild(player, EntityType.WOLF) || Permissions.callOfTheWild(player, EntityType.OCELOT); - canEnvironmentallyAware = canUseSubskill(player, SubSkillType.TAMING_ENVIRONMENTALLY_AWARE); - canFastFood = canUseSubskill(player, SubSkillType.TAMING_FAST_FOOD_SERVICE); - canGore = canUseSubskill(player, SubSkillType.TAMING_GORE); - canSharpenedClaws = canUseSubskill(player, SubSkillType.TAMING_SHARPENED_CLAWS); - canShockProof = canUseSubskill(player, SubSkillType.TAMING_SHOCK_PROOF); - canThickFur = canUseSubskill(player, SubSkillType.TAMING_THICK_FUR); - canHolyHound = canUseSubskill(player, SubSkillType.TAMING_HOLY_HOUND); + canEnvironmentallyAware = Permissions.canUseSubSkill(player, SubSkillType.TAMING_ENVIRONMENTALLY_AWARE); + canFastFood = Permissions.canUseSubSkill(player, SubSkillType.TAMING_FAST_FOOD_SERVICE); + canGore = Permissions.canUseSubSkill(player, SubSkillType.TAMING_GORE); + canSharpenedClaws = Permissions.canUseSubSkill(player, SubSkillType.TAMING_SHARPENED_CLAWS); + canShockProof = Permissions.canUseSubSkill(player, SubSkillType.TAMING_SHOCK_PROOF); + canThickFur = Permissions.canUseSubSkill(player, SubSkillType.TAMING_THICK_FUR); + canHolyHound = Permissions.canUseSubSkill(player, SubSkillType.TAMING_HOLY_HOUND); } @Override diff --git a/src/main/java/com/gmail/nossr50/commands/skills/TridentsCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/TridentsCommand.java new file mode 100644 index 000000000..ff4165659 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/commands/skills/TridentsCommand.java @@ -0,0 +1,62 @@ +package com.gmail.nossr50.commands.skills; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.skills.CombatUtils; +import com.gmail.nossr50.util.skills.SkillUtils; +import com.gmail.nossr50.util.text.TextComponentFactory; +import net.kyori.adventure.text.Component; +import org.bukkit.ChatColor; +import org.bukkit.entity.Player; + +import java.util.ArrayList; +import java.util.List; + +import static com.gmail.nossr50.datatypes.skills.SubSkillType.TRIDENTS_IMPALE; +import static com.gmail.nossr50.datatypes.skills.SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK; + +public class TridentsCommand extends SkillCommand { + + + public TridentsCommand() { + super(PrimarySkillType.TRIDENTS); + } + + @Override + protected void dataCalculations(Player player, float skillValue) {} + + @Override + protected void permissionsCheck(Player player) {} + + @Override + protected List statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) { + List messages = new ArrayList<>(); + McMMOPlayer mmoPlayer = UserManager.getPlayer(player); + if (mmoPlayer == null) { + return messages; + } + + if(SkillUtils.canUseSubskill(player, TRIDENTS_TRIDENTS_LIMIT_BREAK)) { + messages.add(getStatMessage(TRIDENTS_TRIDENTS_LIMIT_BREAK, + String.valueOf(CombatUtils.getLimitBreakDamageAgainstQuality(player, TRIDENTS_TRIDENTS_LIMIT_BREAK, 1000)))); + } + + if(SkillUtils.canUseSubskill(player, TRIDENTS_IMPALE)) { + messages.add(getStatMessage(TRIDENTS_IMPALE, + String.valueOf(mmoPlayer.getTridentsManager().impaleDamageBonus()))); + } + + messages.add(ChatColor.GRAY + "The Tridents skill is a work in progress and is still being developed, feedback would be appreciated in the mcMMO discord server."); + return messages; + } + + @Override + protected List getTextComponents(Player player) { + List textComponents = new ArrayList<>(); + + TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkillType.TRIDENTS); + + return textComponents; + } +} diff --git a/src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java index 5f9784be7..554eca6b3 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java @@ -5,9 +5,9 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -40,7 +40,7 @@ public class UnarmedCommand extends SkillCommand { protected void dataCalculations(Player player, float skillValue) { // UNARMED_ARROW_DEFLECT if (canDeflect) { - String[] deflectStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.UNARMED_ARROW_DEFLECT); + String[] deflectStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.UNARMED_ARROW_DEFLECT); deflectChance = deflectStrings[0]; deflectChanceLucky = deflectStrings[1]; } @@ -54,7 +54,7 @@ public class UnarmedCommand extends SkillCommand { // UNARMED_DISARM if (canDisarm) { - String[] disarmStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.UNARMED_DISARM); + String[] disarmStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.UNARMED_DISARM); disarmChance = disarmStrings[0]; disarmChanceLucky = disarmStrings[1]; } @@ -66,7 +66,7 @@ public class UnarmedCommand extends SkillCommand { // IRON GRIP if (canIronGrip) { - String[] ironGripStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.UNARMED_IRON_GRIP); + String[] ironGripStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.UNARMED_IRON_GRIP); ironGripChance = ironGripStrings[0]; ironGripChanceLucky = ironGripStrings[1]; } @@ -75,10 +75,10 @@ public class UnarmedCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { canBerserk = RankUtils.hasUnlockedSubskill(player, SubSkillType.UNARMED_BERSERK) && Permissions.berserk(player); - canIronArm = canUseSubskill(player, SubSkillType.UNARMED_STEEL_ARM_STYLE); - canDeflect = canUseSubskill(player, SubSkillType.UNARMED_ARROW_DEFLECT); - canDisarm = canUseSubskill(player, SubSkillType.UNARMED_DISARM); - canIronGrip = canUseSubskill(player, SubSkillType.UNARMED_IRON_GRIP); + canIronArm = Permissions.canUseSubSkill(player, SubSkillType.UNARMED_STEEL_ARM_STYLE); + canDeflect = Permissions.canUseSubSkill(player, SubSkillType.UNARMED_ARROW_DEFLECT); + canDisarm = Permissions.canUseSubSkill(player, SubSkillType.UNARMED_DISARM); + canIronGrip = Permissions.canUseSubSkill(player, SubSkillType.UNARMED_IRON_GRIP); // TODO: Apparently we forgot about block cracker? } @@ -114,7 +114,7 @@ public class UnarmedCommand extends SkillCommand { //messages.add(LocaleLoader.getString("Unarmed.Ability.Chance.IronGrip", ironGripChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", ironGripChanceLucky) : "")); } - if(canUseSubskill(player, SubSkillType.UNARMED_UNARMED_LIMIT_BREAK)) { + if(Permissions.canUseSubSkill(player, SubSkillType.UNARMED_UNARMED_LIMIT_BREAK)) { messages.add(getStatMessage(SubSkillType.UNARMED_UNARMED_LIMIT_BREAK, String.valueOf(CombatUtils.getLimitBreakDamageAgainstQuality(player, SubSkillType.UNARMED_UNARMED_LIMIT_BREAK, 1000)))); } diff --git a/src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java b/src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java index 08639c27b..1dd4beeb0 100644 --- a/src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java +++ b/src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java @@ -5,8 +5,8 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.text.TextComponentFactory; import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; @@ -18,15 +18,15 @@ public class WoodcuttingCommand extends SkillCommand { private String treeFellerLength; private String treeFellerLengthEndurance; private String doubleDropChance; + private String tripleDropChance; private String doubleDropChanceLucky; + private String tripleDropChanceLucky; private boolean canTreeFell; private boolean canLeafBlow; private boolean canDoubleDrop; + private boolean canTripleDrop; private boolean canKnockOnWood; - private boolean canSplinter; - private boolean canBarkSurgeon; - private boolean canNaturesBounty; public WoodcuttingCommand() { super(PrimarySkillType.WOODCUTTING); @@ -38,7 +38,14 @@ public class WoodcuttingCommand extends SkillCommand { if (canDoubleDrop) { setDoubleDropClassicChanceStrings(player); } - + + //Clean Cuts + if(canTripleDrop) { + String[] tripleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.WOODCUTTING_CLEAN_CUTS); + tripleDropChance = tripleDropStrings[0]; + tripleDropChanceLucky = tripleDropStrings[1]; + } + // TREE FELLER if (canTreeFell) { String[] treeFellerStrings = calculateLengthDisplayValues(player, skillValue); @@ -48,7 +55,7 @@ public class WoodcuttingCommand extends SkillCommand { } private void setDoubleDropClassicChanceStrings(Player player) { - String[] doubleDropStrings = getAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.WOODCUTTING_HARVEST_LUMBER); + String[] doubleDropStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER); doubleDropChance = doubleDropStrings[0]; doubleDropChanceLucky = doubleDropStrings[1]; } @@ -56,14 +63,12 @@ public class WoodcuttingCommand extends SkillCommand { @Override protected void permissionsCheck(Player player) { canTreeFell = RankUtils.hasUnlockedSubskill(player, SubSkillType.WOODCUTTING_TREE_FELLER) && Permissions.treeFeller(player); - canDoubleDrop = canUseSubskill(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER) - && !mcMMO.p.getGeneralConfig().getDoubleDropsDisabled(skill) + canDoubleDrop = !mcMMO.p.getGeneralConfig().getDoubleDropsDisabled(skill) + && Permissions.canUseSubSkill(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER) && RankUtils.getRank(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER) >= 1; - canLeafBlow = canUseSubskill(player, SubSkillType.WOODCUTTING_LEAF_BLOWER); - canKnockOnWood = canTreeFell && canUseSubskill(player, SubSkillType.WOODCUTTING_KNOCK_ON_WOOD); - /*canSplinter = canUseSubskill(player, SubSkillType.WOODCUTTING_SPLINTER); - canBarkSurgeon = canUseSubskill(player, SubSkillType.WOODCUTTING_BARK_SURGEON); - canNaturesBounty = canUseSubskill(player, SubSkillType.WOODCUTTING_NATURES_BOUNTY);*/ + canTripleDrop = !mcMMO.p.getGeneralConfig().getDoubleDropsDisabled(skill) && Permissions.canUseSubSkill(player, SubSkillType.WOODCUTTING_CLEAN_CUTS); + canLeafBlow = Permissions.canUseSubSkill(player, SubSkillType.WOODCUTTING_LEAF_BLOWER); + canKnockOnWood = canTreeFell && Permissions.canUseSubSkill(player, SubSkillType.WOODCUTTING_KNOCK_ON_WOOD); } @Override @@ -75,6 +80,12 @@ public class WoodcuttingCommand extends SkillCommand { + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", doubleDropChanceLucky) : "")); } + if(canTripleDrop) { + messages.add(getStatMessage(SubSkillType.WOODCUTTING_CLEAN_CUTS, tripleDropChance) + + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", tripleDropChanceLucky) : "")); + } + + if (canKnockOnWood) { String lootNote; diff --git a/src/main/java/com/gmail/nossr50/config/AdvancedConfig.java b/src/main/java/com/gmail/nossr50/config/AdvancedConfig.java index bf985453c..da882f098 100644 --- a/src/main/java/com/gmail/nossr50/config/AdvancedConfig.java +++ b/src/main/java/com/gmail/nossr50/config/AdvancedConfig.java @@ -697,6 +697,15 @@ public class AdvancedConfig extends BukkitConfig { return config.getDouble("Skills.Axes.SkullSplitter.DamageModifier", 2.0D); } + /* CROSSBOWS */ + public double getPoweredShotRankDamageMultiplier() { + return config.getDouble("Skills.Crossbows.PoweredShot.RankDamageMultiplier", 10.0D); + } + + public double getPoweredShotDamageMax() { + return config.getDouble("Skills.Archery.SkillShot.MaxDamage", 9.0D); + } + /* EXCAVATION */ //Nothing to configure, everything is already configurable in config.yml diff --git a/src/main/java/com/gmail/nossr50/config/ChatConfig.java b/src/main/java/com/gmail/nossr50/config/ChatConfig.java index 0e33d74d1..627686efb 100644 --- a/src/main/java/com/gmail/nossr50/config/ChatConfig.java +++ b/src/main/java/com/gmail/nossr50/config/ChatConfig.java @@ -51,6 +51,12 @@ public class ChatConfig extends BukkitConfig { return config.getBoolean(key, true); } + public boolean isConsoleIncludedInAudience(@NotNull ChatChannel chatChannel) { + String key = "Chat.Channels." + StringUtils.getCapitalized(chatChannel.toString()) + ".Send_To_Console"; + return config.getBoolean(key, true); + } + + public boolean isSpyingAutomatic() { return config.getBoolean("Chat.Channels.Party.Spies.Automatically_Enable_Spying", false); } diff --git a/src/main/java/com/gmail/nossr50/config/GeneralConfig.java b/src/main/java/com/gmail/nossr50/config/GeneralConfig.java index 7a6f766c0..801cae25c 100644 --- a/src/main/java/com/gmail/nossr50/config/GeneralConfig.java +++ b/src/main/java/com/gmail/nossr50/config/GeneralConfig.java @@ -1009,4 +1009,6 @@ public class GeneralConfig extends BukkitConfig { public boolean useVerboseLogging() { return config.getBoolean("General.Verbose_Logging", false); } + + public boolean isMasterySystemEnabled() { return config.getBoolean( "General.PowerLevel.Skill_Mastery.Enabled"); } } diff --git a/src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java b/src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java index 929fdcefe..bb7541828 100644 --- a/src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java +++ b/src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java @@ -256,7 +256,7 @@ public class ExperienceConfig extends BukkitConfig { /* Skill modifiers */ public double getFormulaSkillModifier(PrimarySkillType skill) { - return config.getDouble("Experience_Formula.Modifier." + StringUtils.getCapitalized(skill.toString())); + return config.getDouble("Experience_Formula.Modifier." + StringUtils.getCapitalized(skill.toString()), 1); } /* Custom XP perk */ @@ -296,6 +296,10 @@ public class ExperienceConfig extends BukkitConfig { } /* Combat XP Multipliers */ + public double getCombatXP(String entity) { + return config.getDouble("Experience_Values.Combat.Multiplier." + entity); + } + public double getCombatXP(EntityType entity) { return config.getDouble("Experience_Values.Combat.Multiplier." + StringUtils.getPrettyEntityTypeString(entity).replace(" ", "_")); } @@ -314,96 +318,73 @@ public class ExperienceConfig extends BukkitConfig { /* Materials */ public int getXp(PrimarySkillType skill, Material material) { - //TODO: Temporary measure to fix an exploit caused by a yet to be fixed Spigot bug (as of 7/3/2020) - if (material.toString().equalsIgnoreCase("LILY_PAD")) - return 0; - - String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + "."; - String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(material); - if (config.contains(explicitString)) - return config.getInt(explicitString); - String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(material); - if (config.contains(friendlyString)) - return config.getInt(friendlyString); - String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(material); - if (config.contains(wildcardString)) - return config.getInt(wildcardString); - return 0; + return getXpHelper(skill, StringUtils.getExplicitConfigMaterialString(material), + StringUtils.getFriendlyConfigMaterialString(material), + StringUtils.getWildcardConfigMaterialString(material)); } - /* Materials */ public int getXp(PrimarySkillType skill, BlockState blockState) { - Material data = blockState.getType(); - - String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + "."; - String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(data); - if (config.contains(explicitString)) - return config.getInt(explicitString); - String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(data); - if (config.contains(friendlyString)) - return config.getInt(friendlyString); - String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(data); - if (config.contains(wildcardString)) - return config.getInt(wildcardString); - return 0; + Material material = blockState.getType(); + return getXp(skill, material); } - /* Materials */ public int getXp(PrimarySkillType skill, Block block) { - Material data = block.getType(); - - String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + "."; - String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(data); - if (config.contains(explicitString)) - return config.getInt(explicitString); - String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(data); - if (config.contains(friendlyString)) - return config.getInt(friendlyString); - String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(data); - if (config.contains(wildcardString)) - return config.getInt(wildcardString); - return 0; + Material material = block.getType(); + return getXp(skill, material); } - /* Materials */ public int getXp(PrimarySkillType skill, BlockData data) { + return getXpHelper(skill, StringUtils.getExplicitConfigBlockDataString(data), + StringUtils.getFriendlyConfigBlockDataString(data), + StringUtils.getWildcardConfigBlockDataString(data)); + } + + private int getXpHelper(PrimarySkillType skill, String explicitString, String friendlyString, String wildcardString) { + if (explicitString.equalsIgnoreCase("LILY_PAD")) { + return 0; + } + String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + "."; - String explicitString = baseString + StringUtils.getExplicitConfigBlockDataString(data); - if (config.contains(explicitString)) - return config.getInt(explicitString); - String friendlyString = baseString + StringUtils.getFriendlyConfigBlockDataString(data); - if (config.contains(friendlyString)) - return config.getInt(friendlyString); - String wildcardString = baseString + StringUtils.getWildcardConfigBlockDataString(data); - if (config.contains(wildcardString)) - return config.getInt(wildcardString); + String[] configStrings = {explicitString, friendlyString, wildcardString}; + + for (String configString : configStrings) { + String fullPath = baseString + configString; + if (config.contains(fullPath)) { + return config.getInt(fullPath); + } + } + return 0; } - public boolean doesBlockGiveSkillXP(PrimarySkillType skill, Material data) { - String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + "."; - String explicitString = baseString + StringUtils.getExplicitConfigMaterialString(data); - if (config.contains(explicitString)) - return true; - String friendlyString = baseString + StringUtils.getFriendlyConfigMaterialString(data); - if (config.contains(friendlyString)) - return true; - String wildcardString = baseString + StringUtils.getWildcardConfigMaterialString(data); - return config.contains(wildcardString); + + public boolean doesBlockGiveSkillXP(PrimarySkillType skill, Material material) { + return doesBlockGiveSkillXPHelper(skill, StringUtils.getExplicitConfigMaterialString(material), + StringUtils.getFriendlyConfigMaterialString(material), + StringUtils.getWildcardConfigMaterialString(material)); } public boolean doesBlockGiveSkillXP(PrimarySkillType skill, BlockData data) { - String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + "."; - String explicitString = baseString + StringUtils.getExplicitConfigBlockDataString(data); - if (config.contains(explicitString)) - return true; - String friendlyString = baseString + StringUtils.getFriendlyConfigBlockDataString(data); - if (config.contains(friendlyString)) - return true; - String wildcardString = baseString + StringUtils.getWildcardConfigBlockDataString(data); - return config.contains(wildcardString); + return doesBlockGiveSkillXPHelper(skill, StringUtils.getExplicitConfigBlockDataString(data), + StringUtils.getFriendlyConfigBlockDataString(data), + StringUtils.getWildcardConfigBlockDataString(data)); } + private boolean doesBlockGiveSkillXPHelper(PrimarySkillType skill, String explicitString, String friendlyString, String wildcardString) { + String baseString = "Experience_Values." + StringUtils.getCapitalized(skill.toString()) + "."; + String[] configStrings = {explicitString, friendlyString, wildcardString}; + + for (String configString : configStrings) { + String fullPath = baseString + configString; + if (config.contains(fullPath)) { + return true; + } + } + + return false; + } + + /* * Experience Bar Stuff */ diff --git a/src/main/java/com/gmail/nossr50/database/DatabaseManagerFactory.java b/src/main/java/com/gmail/nossr50/database/DatabaseManagerFactory.java index cb5025f6f..df6c5a158 100644 --- a/src/main/java/com/gmail/nossr50/database/DatabaseManagerFactory.java +++ b/src/main/java/com/gmail/nossr50/database/DatabaseManagerFactory.java @@ -10,6 +10,7 @@ import java.util.logging.Logger; public class DatabaseManagerFactory { private static Class customManager = null; + public static final String MYSQL_DRIVER = "com.mysql.cj.jdbc.Driver"; public static DatabaseManager getDatabaseManager(@NotNull String userFilePath, @NotNull Logger logger, long purgeTime, int startingLevel) { if (customManager != null) { @@ -27,7 +28,7 @@ public class DatabaseManagerFactory { LogUtils.debug(mcMMO.p.getLogger(), "Falling back on " + (mcMMO.p.getGeneralConfig().getUseMySQL() ? "SQL" : "Flatfile") + " database"); } - return mcMMO.p.getGeneralConfig().getUseMySQL() ? new SQLDatabaseManager() : new FlatFileDatabaseManager(userFilePath, logger, purgeTime, startingLevel); + return mcMMO.p.getGeneralConfig().getUseMySQL() ? new SQLDatabaseManager(logger, MYSQL_DRIVER) : new FlatFileDatabaseManager(userFilePath, logger, purgeTime, startingLevel); } /** @@ -68,7 +69,7 @@ public class DatabaseManagerFactory { case SQL: LogUtils.debug(mcMMO.p.getLogger(), "Using SQL Database"); - return new SQLDatabaseManager(); + return new SQLDatabaseManager(logger, "com.mysql.cj.jdbc.Driver"); case CUSTOM: try { diff --git a/src/main/java/com/gmail/nossr50/database/FlatFileDataProcessor.java b/src/main/java/com/gmail/nossr50/database/FlatFileDataProcessor.java index a232d86cd..6f8c397e3 100644 --- a/src/main/java/com/gmail/nossr50/database/FlatFileDataProcessor.java +++ b/src/main/java/com/gmail/nossr50/database/FlatFileDataProcessor.java @@ -276,6 +276,8 @@ public class FlatFileDataProcessor { case SKILLS_TAMING: case SKILLS_FISHING: case SKILLS_ALCHEMY: + case SKILLS_CROSSBOWS: + case SKILLS_TRIDENTS: case COOLDOWN_BERSERK: case COOLDOWN_GIGA_DRILL_BREAKER: case COOLDOWN_TREE_FELLER: @@ -286,6 +288,9 @@ public class FlatFileDataProcessor { case COOLDOWN_BLAST_MINING: case SCOREBOARD_TIPS: case COOLDOWN_CHIMAERA_WING: + case COOLDOWN_SUPER_SHOTGUN: + case COOLDOWN_TRIDENTS: + case COOLDOWN_ARCHERY: return ExpectedType.INTEGER; case EXP_MINING: case EXP_WOODCUTTING: @@ -300,6 +305,8 @@ public class FlatFileDataProcessor { case EXP_TAMING: case EXP_FISHING: case EXP_ALCHEMY: + case EXP_CROSSBOWS: + case EXP_TRIDENTS: return ExpectedType.FLOAT; case UUID_INDEX: return ExpectedType.UUID; diff --git a/src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java index 52fa9a330..d4d2e51b6 100644 --- a/src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/FlatFileDatabaseManager.java @@ -80,10 +80,17 @@ public final class FlatFileDatabaseManager implements DatabaseManager { public static final int SCOREBOARD_TIPS = 42; public static final int COOLDOWN_CHIMAERA_WING = 43; public static final int OVERHAUL_LAST_LOGIN = 44; + public static final int EXP_CROSSBOWS = 45; + public static final int SKILLS_CROSSBOWS = 46; + public static final int EXP_TRIDENTS = 47; + public static final int SKILLS_TRIDENTS = 48; + public static final int COOLDOWN_SUPER_SHOTGUN = 49; + public static final int COOLDOWN_TRIDENTS = 50; + public static final int COOLDOWN_ARCHERY = 51; + //Update this everytime new data is added + public static final int DATA_ENTRY_COUNT = COOLDOWN_ARCHERY + 1; - public static final int DATA_ENTRY_COUNT = OVERHAUL_LAST_LOGIN + 1; //Update this everytime new data is added - - protected FlatFileDatabaseManager(@NotNull File usersFile, @NotNull Logger logger, long purgeTime, int startingLevel, boolean testing) { + FlatFileDatabaseManager(@NotNull File usersFile, @NotNull Logger logger, long purgeTime, int startingLevel, boolean testing) { this.usersFile = usersFile; this.usersFilePath = usersFile.getPath(); this.logger = logger; @@ -99,7 +106,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { List flatFileDataFlags = checkFileHealthAndStructure(); if(flatFileDataFlags != null) { - if(flatFileDataFlags.size() > 0) { + if(!flatFileDataFlags.isEmpty()) { logger.info("Detected "+flatFileDataFlags.size() + " data entries which need correction."); } } @@ -108,7 +115,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { } } - protected FlatFileDatabaseManager(@NotNull String usersFilePath, @NotNull Logger logger, long purgeTime, int startingLevel) { + FlatFileDatabaseManager(@NotNull String usersFilePath, @NotNull Logger logger, long purgeTime, int startingLevel) { this(new File(usersFilePath), logger, purgeTime, startingLevel, false); } @@ -237,7 +244,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { out.write(writer.toString()); if(testing) { - System.out.println(writer.toString()); + System.out.println(writer); } } catch (IOException e) { @@ -467,6 +474,10 @@ public final class FlatFileDatabaseManager implements DatabaseManager { appendable.append(String.valueOf(profile.getScoreboardTipsShown())).append(":"); appendable.append(String.valueOf(profile.getUniqueData(UniqueDataType.CHIMAERA_WING_DATS))).append(":"); appendable.append(String.valueOf(profile.getLastLogin())).append(":"); //overhaul last login + appendable.append(String.valueOf(profile.getSkillXpLevel(PrimarySkillType.CROSSBOWS))).append(":"); + appendable.append(String.valueOf(profile.getSkillLevel(PrimarySkillType.CROSSBOWS))).append(":"); + appendable.append(String.valueOf(profile.getSkillXpLevel(PrimarySkillType.TRIDENTS))).append(":"); + appendable.append(String.valueOf(profile.getSkillLevel(PrimarySkillType.TRIDENTS))).append(":"); appendable.append("\r\n"); } @@ -565,16 +576,11 @@ public final class FlatFileDatabaseManager implements DatabaseManager { * @return a profile with the targets data or an unloaded profile if no data was found */ private @NotNull PlayerProfile processUserQuery(@NotNull UserQuery userQuery) throws RuntimeException { - switch(userQuery.getType()) { - case UUID_AND_NAME: - return queryByUUIDAndName((UserQueryFull) userQuery); - case UUID: - return queryByUUID((UserQueryUUID) userQuery); - case NAME: - return queryByName((UserQueryNameImpl) userQuery); - default: - throw new RuntimeException("No case for this UserQueryType!"); - } + return switch (userQuery.getType()) { + case UUID_AND_NAME -> queryByUUIDAndName((UserQueryFull) userQuery); + case UUID -> queryByUUID((UserQueryUUID) userQuery); + case NAME -> queryByName((UserQueryNameImpl) userQuery); + }; } private @NotNull PlayerProfile queryByName(@NotNull UserQueryName userQuery) { @@ -603,8 +609,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { continue; } - - //If we couldn't find anyone + // we found the player if(playerName.equalsIgnoreCase(rawSplitData[USERNAME_INDEX])) { return loadFromLine(rawSplitData); } @@ -681,7 +686,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { * No match was found in the file */ - return grabUnloadedProfile(uuid, "Player-Not-Found="+uuid.toString()); + return grabUnloadedProfile(uuid, "Player-Not-Found="+ uuid); } private @NotNull PlayerProfile queryByUUIDAndName(@NotNull UserQueryFull userQuery) { @@ -716,7 +721,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { boolean matchingName = dbPlayerName.equalsIgnoreCase(playerName); if (!matchingName) { - logger.warning("When loading user: "+playerName +" with UUID of (" + uuid.toString() + logger.warning("When loading user: "+playerName +" with UUID of (" + uuid +") we found a mismatched name, the name in the DB will be replaced (DB name: "+dbPlayerName+")"); //logger.info("Name updated for player: " + rawSplitData[USERNAME_INDEX] + " => " + playerName); rawSplitData[USERNAME_INDEX] = playerName; @@ -980,6 +985,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager { List taming = new ArrayList<>(); List fishing = new ArrayList<>(); List alchemy = new ArrayList<>(); + List crossbows = new ArrayList<>(); + List tridents = new ArrayList<>(); BufferedReader in = null; String playerName = null; @@ -1013,6 +1020,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager { powerLevel += putStat(taming, playerName, skills.get(PrimarySkillType.TAMING)); powerLevel += putStat(unarmed, playerName, skills.get(PrimarySkillType.UNARMED)); powerLevel += putStat(woodcutting, playerName, skills.get(PrimarySkillType.WOODCUTTING)); + powerLevel += putStat(crossbows, playerName, skills.get(PrimarySkillType.CROSSBOWS)); + powerLevel += putStat(tridents, playerName, skills.get(PrimarySkillType.TRIDENTS)); putStat(powerLevels, playerName, powerLevel); } @@ -1048,6 +1057,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager { taming.sort(c); fishing.sort(c); alchemy.sort(c); + crossbows.sort(c); + tridents.sort(c); powerLevels.sort(c); playerStatHash.put(PrimarySkillType.MINING, mining); @@ -1063,6 +1074,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager { playerStatHash.put(PrimarySkillType.TAMING, taming); playerStatHash.put(PrimarySkillType.FISHING, fishing); playerStatHash.put(PrimarySkillType.ALCHEMY, alchemy); + playerStatHash.put(PrimarySkillType.CROSSBOWS, crossbows); + playerStatHash.put(PrimarySkillType.TRIDENTS, tridents); return LeaderboardStatus.UPDATED; } @@ -1094,7 +1107,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { public @Nullable List checkFileHealthAndStructure() { ArrayList flagsFound = null; LogUtils.debug(logger, "(" + usersFile.getPath() + ") Validating database file.."); - FlatFileDataProcessor dataProcessor = null; + FlatFileDataProcessor dataProcessor; if (usersFile.exists()) { BufferedReader bufferedReader = null; @@ -1126,7 +1139,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { } //Only update the file if needed - if(dataProcessor.getFlatFileDataFlags().size() > 0) { + if(!dataProcessor.getFlatFileDataFlags().isEmpty()) { flagsFound = new ArrayList<>(dataProcessor.getFlatFileDataFlags()); logger.info("Updating FlatFile Database..."); fileWriter = new FileWriter(usersFilePath); @@ -1144,7 +1157,7 @@ public final class FlatFileDatabaseManager implements DatabaseManager { } } - if(flagsFound == null || flagsFound.size() == 0) { + if(flagsFound == null || flagsFound.isEmpty()) { return null; } else { return flagsFound; @@ -1224,6 +1237,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager { tryLoadSkillFloatValuesFromRawData(skillsXp, character, PrimarySkillType.ACROBATICS, EXP_ACROBATICS, username); tryLoadSkillFloatValuesFromRawData(skillsXp, character, PrimarySkillType.FISHING, EXP_FISHING, username); tryLoadSkillFloatValuesFromRawData(skillsXp, character, PrimarySkillType.ALCHEMY, EXP_ALCHEMY, username); + tryLoadSkillFloatValuesFromRawData(skillsXp, character, PrimarySkillType.CROSSBOWS, EXP_CROSSBOWS, username); + tryLoadSkillFloatValuesFromRawData(skillsXp, character, PrimarySkillType.TRIDENTS, EXP_TRIDENTS, username); // Taming - Unused tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.SUPER_BREAKER, COOLDOWN_SUPER_BREAKER, username); @@ -1232,11 +1247,13 @@ public final class FlatFileDatabaseManager implements DatabaseManager { tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.BERSERK, COOLDOWN_BERSERK, username); tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.GREEN_TERRA, COOLDOWN_GREEN_TERRA, username); tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.GIGA_DRILL_BREAKER, COOLDOWN_GIGA_DRILL_BREAKER, username); - // Archery - Unused + tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.EXPLOSIVE_SHOT, COOLDOWN_ARCHERY, username); tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.SERRATED_STRIKES, COOLDOWN_SERRATED_STRIKES, username); tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.SKULL_SPLITTER, COOLDOWN_SKULL_SPLITTER, username); // Acrobatics - Unused tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.BLAST_MINING, COOLDOWN_BLAST_MINING, username); + tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.SUPER_SHOTGUN, COOLDOWN_SUPER_SHOTGUN, username); + tryLoadSkillCooldownFromRawData(skillsDATS, character, SuperAbilityType.TRIDENTS_SUPER_ABILITY, COOLDOWN_TRIDENTS, username); UUID uuid; try { @@ -1269,12 +1286,15 @@ public final class FlatFileDatabaseManager implements DatabaseManager { return new PlayerProfile(username, uuid, skills, skillsXp, skillsDATS, scoreboardTipsShown, uniquePlayerDataMap, lastLogin); } - private void tryLoadSkillCooldownFromRawData(@NotNull Map cooldownMap, @NotNull String[] character, @NotNull SuperAbilityType superAbilityType, int cooldownSuperBreaker, @NotNull String userName) { + private void tryLoadSkillCooldownFromRawData(@NotNull Map cooldownMap, @NotNull String[] splitData, @NotNull SuperAbilityType superAbilityType, int index, @NotNull String userName) { try { - cooldownMap.put(superAbilityType, Integer.valueOf(character[cooldownSuperBreaker])); + cooldownMap.put(superAbilityType, Integer.valueOf(splitData[index])); + } catch (IndexOutOfBoundsException e) { + // TODO: Add debug message + // set to 0 when data not found + cooldownMap.put(superAbilityType, 0); } catch (NumberFormatException e) { - logger.severe("Data corruption when trying to load the value for skill "+superAbilityType+" for player named " + userName+ " setting value to zero"); - e.printStackTrace(); + throw new NumberFormatException("Data corruption when trying to load the cooldown for skill "+superAbilityType+" for player named " + userName); } } @@ -1293,6 +1313,10 @@ public final class FlatFileDatabaseManager implements DatabaseManager { try { int valueFromString = Integer.parseInt(character[index]); skillMap.put(primarySkillType, valueFromString); + } catch (ArrayIndexOutOfBoundsException e ) { + // TODO: Add debug message + // set to 0 when data not found + skillMap.put(primarySkillType, 0); } catch (NumberFormatException e) { skillMap.put(primarySkillType, 0); logger.severe("Data corruption when trying to load the value for skill "+primarySkillType+" for player named " + userName+ " setting value to zero"); @@ -1317,6 +1341,8 @@ public final class FlatFileDatabaseManager implements DatabaseManager { tryLoadSkillIntValuesFromRawData(skills, character, PrimarySkillType.AXES, SKILLS_AXES, username); tryLoadSkillIntValuesFromRawData(skills, character, PrimarySkillType.FISHING, SKILLS_FISHING, username); tryLoadSkillIntValuesFromRawData(skills, character, PrimarySkillType.ALCHEMY, SKILLS_ALCHEMY, username); + tryLoadSkillIntValuesFromRawData(skills, character, PrimarySkillType.CROSSBOWS, SKILLS_CROSSBOWS, username); + tryLoadSkillIntValuesFromRawData(skills, character, PrimarySkillType.TRIDENTS, SKILLS_TRIDENTS, username); return skills; } diff --git a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java index 0be013e7e..64aeab1ad 100644 --- a/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java +++ b/src/main/java/com/gmail/nossr50/database/SQLDatabaseManager.java @@ -24,6 +24,7 @@ import org.jetbrains.annotations.Nullable; import java.sql.*; import java.util.*; import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; public final class SQLDatabaseManager implements DatabaseManager { private static final String ALL_QUERY_VERSION = "total"; @@ -32,6 +33,7 @@ public final class SQLDatabaseManager implements DatabaseManager { public static final String USER_VARCHAR = "VARCHAR(40)"; public static final int CHILD_SKILLS_SIZE = 2; public static final String LEGACY_DRIVER_PATH = "com.mysql.jdbc.Driver"; + public static final int MAGIC_NUMBER = 44; private final String tablePrefix = mcMMO.p.getGeneralConfig().getMySQLTablePrefix(); private final Map cachedUserIDs = new HashMap<>(); @@ -45,23 +47,19 @@ public final class SQLDatabaseManager implements DatabaseManager { private final ReentrantLock massUpdateLock = new ReentrantLock(); private final String CHARSET_SQL = "utf8mb4"; //This is compliant with UTF-8 while "utf8" is not, confusing but this is how it is. - private String driverPath = "com.mysql.cj.jdbc.Driver"; //modern driver + private final Logger logger; + private final boolean h2; - protected SQLDatabaseManager() { - String connectionString = "jdbc:mysql://" + mcMMO.p.getGeneralConfig().getMySQLServerName() - + ":" + mcMMO.p.getGeneralConfig().getMySQLServerPort() + "/" + mcMMO.p.getGeneralConfig().getMySQLDatabaseName(); + SQLDatabaseManager(Logger logger, String driverPath) { + this(logger, driverPath, false); + } - if(!mcMMO.getCompatibilityManager().getMinecraftGameVersion().isAtLeast(1, 17, 0) //Temporary hack for SQL and 1.17 support - && mcMMO.p.getGeneralConfig().getMySQLSSL()) - connectionString += - "?verifyServerCertificate=false"+ - "&useSSL=true"+ - "&requireSSL=true"; - else - connectionString+= - "?useSSL=false"; + SQLDatabaseManager(Logger logger, String driverPath, boolean h2) { + this.logger = logger; + this.h2 = h2; + String connectionString = getConnectionString(h2); - if(mcMMO.p.getGeneralConfig().getMySQLPublicKeyRetrieval()) { + if(!h2 && mcMMO.p.getGeneralConfig().getMySQLPublicKeyRetrieval()) { connectionString+= "&allowPublicKeyRetrieval=true"; } @@ -76,7 +74,7 @@ public final class SQLDatabaseManager implements DatabaseManager { } catch (ClassNotFoundException ex) { e.printStackTrace(); ex.printStackTrace(); - mcMMO.p.getLogger().severe("Neither driver found"); + logger.severe("Neither driver found"); return; } //throw e; // aborts onEnable() Riking if you want to do this, fully implement it. @@ -133,9 +131,31 @@ public final class SQLDatabaseManager implements DatabaseManager { checkStructure(); } + @NotNull + private static String getConnectionString(boolean h2) { + if (h2) { + return "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;MODE=MySQL"; + } + + String connectionString = "jdbc:mysql://" + mcMMO.p.getGeneralConfig().getMySQLServerName() + + ":" + mcMMO.p.getGeneralConfig().getMySQLServerPort() + "/" + mcMMO.p.getGeneralConfig().getMySQLDatabaseName(); + + if(!mcMMO.getCompatibilityManager().getMinecraftGameVersion().isAtLeast(1, 17, 0) //Temporary hack for SQL and 1.17 support + && mcMMO.p.getGeneralConfig().getMySQLSSL()) + connectionString += + "?verifyServerCertificate=false"+ + "&useSSL=true"+ + "&requireSSL=true"; + else + connectionString+= + "?useSSL=false"; + return connectionString; + } + + // TODO: unit tests public int purgePowerlessUsers() { massUpdateLock.lock(); - mcMMO.p.getLogger().info("Purging powerless users..."); + logger.info("Purging powerless users..."); Connection connection = null; Statement statement = null; @@ -149,7 +169,7 @@ public final class SQLDatabaseManager implements DatabaseManager { + "taming = 0 AND mining = 0 AND woodcutting = 0 AND repair = 0 " + "AND unarmed = 0 AND herbalism = 0 AND excavation = 0 AND " + "archery = 0 AND swords = 0 AND axes = 0 AND acrobatics = 0 " - + "AND fishing = 0 AND alchemy = 0;"); + + "AND fishing = 0 AND alchemy = 0 AND crossbows = 0 AND tridents = 0;"); statement.executeUpdate("DELETE FROM `" + tablePrefix + "experience` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "skills` `s` WHERE `" + tablePrefix + "experience`.`user_id` = `s`.`user_id`)"); statement.executeUpdate("DELETE FROM `" + tablePrefix + "huds` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "skills` `s` WHERE `" + tablePrefix + "huds`.`user_id` = `s`.`user_id`)"); @@ -165,13 +185,13 @@ public final class SQLDatabaseManager implements DatabaseManager { massUpdateLock.unlock(); } - mcMMO.p.getLogger().info("Purged " + purged + " users from the database."); + logger.info("Purged " + purged + " users from the database."); return purged; } public void purgeOldUsers() { massUpdateLock.lock(); - mcMMO.p.getLogger().info("Purging inactive users older than " + (mcMMO.p.getPurgeTime() / 2630000000L) + " months..."); + logger.info("Purging inactive users older than " + (mcMMO.p.getPurgeTime() / 2630000000L) + " months..."); Connection connection = null; Statement statement = null; @@ -196,7 +216,7 @@ public final class SQLDatabaseManager implements DatabaseManager { massUpdateLock.unlock(); } - mcMMO.p.getLogger().info("Purged " + purged + " users from the database."); + logger.info("Purged " + purged + " users from the database."); } public boolean removeUser(String playerName, UUID uuid) { @@ -212,7 +232,7 @@ public final class SQLDatabaseManager implements DatabaseManager { "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + - "WHERE u.user = ?"); + "WHERE u.`user` = ?"); statement.setString(1, playerName); @@ -253,7 +273,7 @@ public final class SQLDatabaseManager implements DatabaseManager { if (id == -1) { id = newUser(connection, profile.getPlayerName(), profile.getUniqueId()); if (id == -1) { - mcMMO.p.getLogger().severe("Failed to create new account for " + profile.getPlayerName()); + logger.severe("Failed to create new account for " + profile.getPlayerName()); return false; } } @@ -263,7 +283,7 @@ public final class SQLDatabaseManager implements DatabaseManager { success &= (statement.executeUpdate() != 0); statement.close(); if (!success) { - mcMMO.p.getLogger().severe("Failed to update last login for " + profile.getPlayerName()); + logger.severe("Failed to update last login for " + profile.getPlayerName()); return false; } @@ -271,7 +291,7 @@ public final class SQLDatabaseManager implements DatabaseManager { + " taming = ?, mining = ?, repair = ?, woodcutting = ?" + ", unarmed = ?, herbalism = ?, excavation = ?" + ", archery = ?, swords = ?, axes = ?, acrobatics = ?" - + ", fishing = ?, alchemy = ?, total = ? WHERE user_id = ?"); + + ", fishing = ?, alchemy = ?, crossbows = ?, tridents = ?, total = ? WHERE user_id = ?"); statement.setInt(1, profile.getSkillLevel(PrimarySkillType.TAMING)); statement.setInt(2, profile.getSkillLevel(PrimarySkillType.MINING)); statement.setInt(3, profile.getSkillLevel(PrimarySkillType.REPAIR)); @@ -285,15 +305,17 @@ public final class SQLDatabaseManager implements DatabaseManager { statement.setInt(11, profile.getSkillLevel(PrimarySkillType.ACROBATICS)); statement.setInt(12, profile.getSkillLevel(PrimarySkillType.FISHING)); statement.setInt(13, profile.getSkillLevel(PrimarySkillType.ALCHEMY)); + statement.setInt(14, profile.getSkillLevel(PrimarySkillType.CROSSBOWS)); + statement.setInt(15, profile.getSkillLevel(PrimarySkillType.TRIDENTS)); int total = 0; for (PrimarySkillType primarySkillType : SkillTools.NON_CHILD_SKILLS) total += profile.getSkillLevel(primarySkillType); - statement.setInt(14, total); - statement.setInt(15, id); + statement.setInt(16, total); + statement.setInt(17, id); success &= (statement.executeUpdate() != 0); statement.close(); if (!success) { - mcMMO.p.getLogger().severe("Failed to update skills for " + profile.getPlayerName()); + logger.severe("Failed to update skills for " + profile.getPlayerName()); return false; } @@ -301,7 +323,7 @@ public final class SQLDatabaseManager implements DatabaseManager { + " taming = ?, mining = ?, repair = ?, woodcutting = ?" + ", unarmed = ?, herbalism = ?, excavation = ?" + ", archery = ?, swords = ?, axes = ?, acrobatics = ?" - + ", fishing = ?, alchemy = ? WHERE user_id = ?"); + + ", fishing = ?, alchemy = ?, crossbows = ?, tridents = ? WHERE user_id = ?"); statement.setInt(1, profile.getSkillXpLevel(PrimarySkillType.TAMING)); statement.setInt(2, profile.getSkillXpLevel(PrimarySkillType.MINING)); statement.setInt(3, profile.getSkillXpLevel(PrimarySkillType.REPAIR)); @@ -315,18 +337,20 @@ public final class SQLDatabaseManager implements DatabaseManager { statement.setInt(11, profile.getSkillXpLevel(PrimarySkillType.ACROBATICS)); statement.setInt(12, profile.getSkillXpLevel(PrimarySkillType.FISHING)); statement.setInt(13, profile.getSkillXpLevel(PrimarySkillType.ALCHEMY)); - statement.setInt(14, id); + statement.setInt(14, profile.getSkillXpLevel(PrimarySkillType.CROSSBOWS)); + statement.setInt(15, profile.getSkillXpLevel(PrimarySkillType.TRIDENTS)); + statement.setInt(16, id); success &= (statement.executeUpdate() != 0); statement.close(); if (!success) { - mcMMO.p.getLogger().severe("Failed to update experience for " + profile.getPlayerName()); + logger.severe("Failed to update experience for " + profile.getPlayerName()); return false; } statement = connection.prepareStatement("UPDATE " + tablePrefix + "cooldowns SET " + " mining = ?, woodcutting = ?, unarmed = ?" + ", herbalism = ?, excavation = ?, swords = ?" - + ", axes = ?, blast_mining = ?, chimaera_wing = ? WHERE user_id = ?"); + + ", axes = ?, blast_mining = ?, chimaera_wing = ?, crossbows = ?, tridents = ? WHERE user_id = ?"); statement.setLong(1, profile.getAbilityDATS(SuperAbilityType.SUPER_BREAKER)); statement.setLong(2, profile.getAbilityDATS(SuperAbilityType.TREE_FELLER)); statement.setLong(3, profile.getAbilityDATS(SuperAbilityType.BERSERK)); @@ -336,11 +360,13 @@ public final class SQLDatabaseManager implements DatabaseManager { statement.setLong(7, profile.getAbilityDATS(SuperAbilityType.SKULL_SPLITTER)); statement.setLong(8, profile.getAbilityDATS(SuperAbilityType.BLAST_MINING)); statement.setLong(9, profile.getUniqueData(UniqueDataType.CHIMAERA_WING_DATS)); - statement.setInt(10, id); + statement.setLong(10, profile.getAbilityDATS(SuperAbilityType.SUPER_SHOTGUN)); + statement.setLong(11, profile.getAbilityDATS(SuperAbilityType.TRIDENTS_SUPER_ABILITY)); + statement.setInt(12, id); success = (statement.executeUpdate() != 0); statement.close(); if (!success) { - mcMMO.p.getLogger().severe("Failed to update cooldowns for " + profile.getPlayerName()); + logger.severe("Failed to update cooldowns for " + profile.getPlayerName()); return false; } @@ -351,7 +377,7 @@ public final class SQLDatabaseManager implements DatabaseManager { success = (statement.executeUpdate() != 0); statement.close(); if (!success) { - mcMMO.p.getLogger().severe("Failed to update hud settings for " + profile.getPlayerName()); + logger.severe("Failed to update hud settings for " + profile.getPlayerName()); return false; } } @@ -371,11 +397,10 @@ public final class SQLDatabaseManager implements DatabaseManager { //Fix for a plugin that people are using that is throwing SQL errors if(skill != null && SkillTools.isChildSkill(skill)) { - mcMMO.p.getLogger().severe("A plugin hooking into mcMMO is being naughty with our database commands, update all plugins that hook into mcMMO and contact their devs!"); + logger.severe("A plugin hooking into mcMMO is being naughty with our database commands, update all plugins that hook into mcMMO and contact their devs!"); throw new InvalidSkillException("A plugin hooking into mcMMO that you are using is attempting to read leaderboard skills for child skills, child skills do not have leaderboards! This is NOT an mcMMO error!"); } - String query = skill == null ? ALL_QUERY_VERSION : skill.name().toLowerCase(Locale.ENGLISH); ResultSet resultSet = null; PreparedStatement statement = null; @@ -383,7 +408,7 @@ public final class SQLDatabaseManager implements DatabaseManager { try { connection = getConnection(PoolIdentifier.MISC); - statement = connection.prepareStatement("SELECT " + query + ", user FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON (user_id = id) WHERE " + query + " > 0 AND NOT user = '\\_INVALID\\_OLD\\_USERNAME\\_' ORDER BY " + query + " DESC, user LIMIT ?, ?"); + statement = connection.prepareStatement("SELECT " + query + ", `user` FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON (user_id = id) WHERE " + query + " > 0 AND NOT `user` = '\\_INVALID\\_OLD\\_USERNAME\\_' ORDER BY " + query + " DESC, `user` LIMIT ?, ?"); statement.setInt(1, (pageNumber * statsPerPage) - statsPerPage); statement.setInt(2, statsPerPage); resultSet = statement.executeQuery(); @@ -424,7 +449,7 @@ public final class SQLDatabaseManager implements DatabaseManager { // Get count of all users with higher skill level than player String sql = "SELECT COUNT(*) AS 'rank' FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " + "AND " + skillName + " > (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " + - "WHERE user = ?)"; + "WHERE `user` = ?)"; statement = connection.prepareStatement(sql); statement.setString(1, playerName); @@ -437,7 +462,7 @@ public final class SQLDatabaseManager implements DatabaseManager { // Ties are settled by alphabetical order sql = "SELECT user, " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE " + skillName + " > 0 " + "AND " + skillName + " = (SELECT " + skillName + " FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id " + - "WHERE user = '" + playerName + "') ORDER BY user"; + "WHERE `user` = '" + playerName + "') ORDER BY user"; resultSet.close(); statement.close(); @@ -460,7 +485,7 @@ public final class SQLDatabaseManager implements DatabaseManager { "WHERE " + ALL_QUERY_VERSION + " > 0 " + "AND " + ALL_QUERY_VERSION + " > " + "(SELECT " + ALL_QUERY_VERSION + " " + - "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?)"; + "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE `user` = ?)"; statement = connection.prepareStatement(sql); statement.setString(1, playerName); @@ -478,7 +503,7 @@ public final class SQLDatabaseManager implements DatabaseManager { "WHERE " + ALL_QUERY_VERSION + " > 0 " + "AND " + ALL_QUERY_VERSION + " = " + "(SELECT " + ALL_QUERY_VERSION + " " + - "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE user = ?) ORDER BY user"; + "FROM " + tablePrefix + "users JOIN " + tablePrefix + "skills ON user_id = id WHERE `user` = ?) ORDER BY user"; statement = connection.prepareStatement(sql); statement.setString(1, playerName); @@ -546,21 +571,26 @@ public final class SQLDatabaseManager implements DatabaseManager { try { statement = connection.prepareStatement( "UPDATE `" + tablePrefix + "users` " - + "SET user = ? " - + "WHERE user = ?"); + + "SET `user` = ? " + + "WHERE `user` = ?"); statement.setString(1, "_INVALID_OLD_USERNAME_"); statement.setString(2, playerName); statement.executeUpdate(); statement.close(); - statement = connection.prepareStatement("INSERT INTO " + tablePrefix + "users (user, uuid, lastlogin) VALUES (?, ?, UNIX_TIMESTAMP())", Statement.RETURN_GENERATED_KEYS); + + long currentTimeMillis = System.currentTimeMillis(); + + String sql = "INSERT INTO " + tablePrefix + "users (`user`, uuid, lastlogin) VALUES (?, ?, ?)"; + statement = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); statement.setString(1, playerName); statement.setString(2, uuid != null ? uuid.toString() : null); + statement.setLong(3, currentTimeMillis); statement.executeUpdate(); resultSet = statement.getGeneratedKeys(); if (!resultSet.next()) { - mcMMO.p.getLogger().severe("Unable to create new user account in DB"); + logger.severe("Unable to create new user account in DB"); return -1; } @@ -600,7 +630,6 @@ public final class SQLDatabaseManager implements DatabaseManager { return loadPlayerFromDB(uuid, null); } - private PlayerProfile loadPlayerFromDB(@Nullable UUID uuid, @Nullable String playerName) throws RuntimeException { if(uuid == null && playerName == null) { throw new RuntimeException("Error looking up player, both UUID and playerName are null and one must not be."); @@ -622,17 +651,18 @@ public final class SQLDatabaseManager implements DatabaseManager { writeMissingRows(connection, id); statement = connection.prepareStatement( - "SELECT " - + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, " - + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, " - + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, c.chimaera_wing, " - + "h.mobhealthbar, h.scoreboardtips, u.uuid, u.user " + "SELECT " + + "s.taming, s.mining, s.repair, s.woodcutting, s.unarmed, s.herbalism, s.excavation, s.archery, s.swords, s.axes, s.acrobatics, s.fishing, s.alchemy, s.crossbows, s.tridents, " + + "e.taming, e.mining, e.repair, e.woodcutting, e.unarmed, e.herbalism, e.excavation, e.archery, e.swords, e.axes, e.acrobatics, e.fishing, e.alchemy, e.crossbows, e.tridents, " + + "c.taming, c.mining, c.repair, c.woodcutting, c.unarmed, c.herbalism, c.excavation, c.archery, c.swords, c.axes, c.acrobatics, c.blast_mining, c.chimaera_wing, c.crossbows, c.tridents, " + + "h.mobhealthbar, h.scoreboardtips, u.uuid, u.`user` " + "FROM " + tablePrefix + "users u " + "JOIN " + tablePrefix + "skills s ON (u.id = s.user_id) " + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " - + "WHERE u.id = ?"); + + "WHERE u.id = ?" + ); statement.setInt(1, id); resultSet = statement.executeQuery(); @@ -640,7 +670,7 @@ public final class SQLDatabaseManager implements DatabaseManager { if (resultSet.next()) { try { PlayerProfile profile = loadFromResult(playerName, resultSet); - String name = resultSet.getString(42); // TODO: Magic Number, make sure it stays updated + String name = resultSet.getString(MAGIC_NUMBER); // TODO: Magic Number, make sure it stays updated resultSet.close(); statement.close(); @@ -650,15 +680,15 @@ public final class SQLDatabaseManager implements DatabaseManager { && uuid != null) { statement = connection.prepareStatement( "UPDATE `" + tablePrefix + "users` " - + "SET user = ? " - + "WHERE user = ?"); + + "SET `user` = ? " + + "WHERE `user` = ?"); statement.setString(1, "_INVALID_OLD_USERNAME_"); statement.setString(2, name); statement.executeUpdate(); statement.close(); statement = connection.prepareStatement( "UPDATE `" + tablePrefix + "users` " - + "SET user = ?, uuid = ? " + + "SET `user` = ?, uuid = ? " + "WHERE id = ?"); statement.setString(1, playerName); statement.setString(2, uuid.toString()); @@ -706,7 +736,7 @@ public final class SQLDatabaseManager implements DatabaseManager { + "JOIN " + tablePrefix + "experience e ON (u.id = e.user_id) " + "JOIN " + tablePrefix + "cooldowns c ON (u.id = c.user_id) " + "JOIN " + tablePrefix + "huds h ON (u.id = h.user_id) " - + "WHERE u.user = ?"); + + "WHERE u.`user` = ?"); List usernames = getStoredUsers(); int convertedUsers = 0; long startMillis = System.currentTimeMillis(); @@ -745,7 +775,7 @@ public final class SQLDatabaseManager implements DatabaseManager { connection = getConnection(PoolIdentifier.MISC); statement = connection.prepareStatement( "UPDATE `" + tablePrefix + "users` SET " - + " uuid = ? WHERE user = ?"); + + " uuid = ? WHERE `user` = ?"); statement.setString(1, uuid.toString()); statement.setString(2, userName); statement.execute(); @@ -769,7 +799,7 @@ public final class SQLDatabaseManager implements DatabaseManager { try { connection = getConnection(PoolIdentifier.MISC); - statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE user = ?"); + statement = connection.prepareStatement("UPDATE " + tablePrefix + "users SET uuid = ? WHERE `user` = ?"); for (Map.Entry entry : fetchedUUIDs.entrySet()) { statement.setString(1, entry.getValue().toString()); @@ -811,7 +841,7 @@ public final class SQLDatabaseManager implements DatabaseManager { try { connection = getConnection(PoolIdentifier.MISC); statement = connection.createStatement(); - resultSet = statement.executeQuery("SELECT user FROM " + tablePrefix + "users"); + resultSet = statement.executeQuery("SELECT `user` FROM " + tablePrefix + "users"); while (resultSet.next()) { users.add(resultSet.getString("user")); } @@ -832,7 +862,6 @@ public final class SQLDatabaseManager implements DatabaseManager { * Checks that the database structure is present and correct */ private void checkStructure() { - PreparedStatement statement = null; Statement createStatement = null; ResultSet resultSet = null; @@ -840,27 +869,30 @@ public final class SQLDatabaseManager implements DatabaseManager { try { connection = getConnection(PoolIdentifier.MISC); - statement = connection.prepareStatement("SELECT table_name FROM INFORMATION_SCHEMA.TABLES" - + " WHERE table_schema = ?" - + " AND table_name = ?"); - statement.setString(1, mcMMO.p.getGeneralConfig().getMySQLDatabaseName()); - statement.setString(2, tablePrefix + "users"); + String schemaQuery = this.h2 ? "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_name = ?" + : "SELECT table_name FROM INFORMATION_SCHEMA.TABLES WHERE table_schema = ? AND table_name = ?"; + + statement = connection.prepareStatement(schemaQuery); + + setStatementQuery(statement, "users"); + resultSet = statement.executeQuery(); + if (!resultSet.next()) { createStatement = connection.createStatement(); - createStatement.executeUpdate("CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` (" - + "`id` int(10) unsigned NOT NULL AUTO_INCREMENT," - + "`user` varchar(40) NOT NULL," - + "`uuid` varchar(36) NULL DEFAULT NULL," - + "`lastlogin` int(32) unsigned NOT NULL," - + "PRIMARY KEY (`id`)," - + "INDEX(`user`(20) ASC)," - + "UNIQUE KEY `uuid` (`uuid`)) DEFAULT CHARSET=" + CHARSET_SQL + " AUTO_INCREMENT=1;"); + String sql = "CREATE TABLE IF NOT EXISTS `" + tablePrefix + "users` (" + + "`id` int AUTO_INCREMENT," + + "`user` varchar(40) NOT NULL," + + "`uuid` varchar(36)," + + "`lastlogin` bigint NOT NULL," + + "PRIMARY KEY (`id`)," + + "INDEX `user_index`(`user`)," + + "UNIQUE(`uuid`))"; + createStatement.executeUpdate(sql); tryClose(createStatement); } tryClose(resultSet); - statement.setString(1, mcMMO.p.getGeneralConfig().getMySQLDatabaseName()); - statement.setString(2, tablePrefix + "huds"); + setStatementQuery(statement, "huds"); resultSet = statement.executeQuery(); if (!resultSet.next()) { createStatement = connection.createStatement(); @@ -873,8 +905,7 @@ public final class SQLDatabaseManager implements DatabaseManager { tryClose(createStatement); } tryClose(resultSet); - statement.setString(1, mcMMO.p.getGeneralConfig().getMySQLDatabaseName()); - statement.setString(2, tablePrefix + "cooldowns"); + setStatementQuery(statement, "cooldowns"); resultSet = statement.executeQuery(); if (!resultSet.next()) { createStatement = connection.createStatement(); @@ -893,13 +924,14 @@ public final class SQLDatabaseManager implements DatabaseManager { + "`acrobatics` int(32) unsigned NOT NULL DEFAULT '0'," + "`blast_mining` int(32) unsigned NOT NULL DEFAULT '0'," + "`chimaera_wing` int(32) unsigned NOT NULL DEFAULT '0'," + + "`crossbows` int(32) unsigned NOT NULL DEFAULT '0'," + + "`tridents` int(32) unsigned NOT NULL DEFAULT '0'," + "PRIMARY KEY (`user_id`)) " + "DEFAULT CHARSET=" + CHARSET_SQL + ";"); tryClose(createStatement); } tryClose(resultSet); - statement.setString(1, mcMMO.p.getGeneralConfig().getMySQLDatabaseName()); - statement.setString(2, tablePrefix + "skills"); + setStatementQuery(statement, "skills"); resultSet = statement.executeQuery(); if (!resultSet.next()) { String startingLevel = "'" + mcMMO.p.getAdvancedConfig().getStartingLevel() + "'"; @@ -920,14 +952,15 @@ public final class SQLDatabaseManager implements DatabaseManager { + "`acrobatics` int(10) unsigned NOT NULL DEFAULT "+startingLevel+"," + "`fishing` int(10) unsigned NOT NULL DEFAULT "+startingLevel+"," + "`alchemy` int(10) unsigned NOT NULL DEFAULT "+startingLevel+"," + + "`crossbows` int(10) unsigned NOT NULL DEFAULT "+startingLevel+"," + + "`tridents` int(10) unsigned NOT NULL DEFAULT "+startingLevel+"," + "`total` int(10) unsigned NOT NULL DEFAULT "+totalLevel+"," + "PRIMARY KEY (`user_id`)) " + "DEFAULT CHARSET=" + CHARSET_SQL + ";"); tryClose(createStatement); } tryClose(resultSet); - statement.setString(1, mcMMO.p.getGeneralConfig().getMySQLDatabaseName()); - statement.setString(2, tablePrefix + "experience"); + setStatementQuery(statement, "experience"); resultSet = statement.executeQuery(); if (!resultSet.next()) { createStatement = connection.createStatement(); @@ -946,6 +979,8 @@ public final class SQLDatabaseManager implements DatabaseManager { + "`acrobatics` int(10) unsigned NOT NULL DEFAULT '0'," + "`fishing` int(10) unsigned NOT NULL DEFAULT '0'," + "`alchemy` int(10) unsigned NOT NULL DEFAULT '0'," + + "`crossbows` int(10) unsigned NOT NULL DEFAULT '0'," + + "`tridents` int(10) unsigned NOT NULL DEFAULT '0'," + "PRIMARY KEY (`user_id`)) " + "DEFAULT CHARSET=" + CHARSET_SQL + ";"); tryClose(createStatement); @@ -968,7 +1003,8 @@ public final class SQLDatabaseManager implements DatabaseManager { } } - mcMMO.p.getLogger().info("Killing orphans"); + // TODO: refactor + LogUtils.debug(logger, "Killing orphans"); createStatement = connection.createStatement(); createStatement.executeUpdate("DELETE FROM `" + tablePrefix + "experience` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "experience`.`user_id` = `u`.`id`)"); createStatement.executeUpdate("DELETE FROM `" + tablePrefix + "huds` WHERE NOT EXISTS (SELECT * FROM `" + tablePrefix + "users` `u` WHERE `" + tablePrefix + "huds`.`user_id` = `u`.`id`)"); @@ -985,21 +1021,53 @@ public final class SQLDatabaseManager implements DatabaseManager { tryClose(connection); } + String skills = "skills"; + String crossbows = "crossbows"; + String tridents = "tridents"; + String experience = "experience"; + String cooldowns = "cooldowns"; + + updateStructure(skills, crossbows, String.valueOf(32)); + updateStructure(skills, tridents, String.valueOf(32)); + + updateStructure(experience, crossbows, String.valueOf(10)); + updateStructure(experience, tridents, String.valueOf(10)); + + updateStructure(cooldowns, crossbows, String.valueOf(10)); + updateStructure(cooldowns, tridents, String.valueOf(10)); } - private Connection getConnection(PoolIdentifier identifier) throws SQLException { - Connection connection = null; - switch (identifier) { - case LOAD: - connection = loadPool.getConnection(); - break; - case MISC: - connection = miscPool.getConnection(); - break; - case SAVE: - connection = savePool.getConnection(); - break; + private void updateStructure(String tableName, String columnName, String columnSize) { + try (Connection connection = getConnection(PoolIdentifier.MISC); + Statement createStatement = connection.createStatement()) { + + String startingLevel = "'" + mcMMO.p.getAdvancedConfig().getStartingLevel() + "'"; + createStatement.executeUpdate("ALTER TABLE `" + tablePrefix + tableName + "` " + + "ADD COLUMN IF NOT EXISTS `" + columnName + "` int(" + columnSize + ") unsigned NOT NULL DEFAULT " + startingLevel); + + } catch (SQLException e) { + e.printStackTrace(); // Consider more robust logging + throw new RuntimeException(e); } + } + + private void setStatementQuery(PreparedStatement statement, String tableName) throws SQLException { + if (!this.h2) { + // Set schema name for MySQL + statement.setString(1, mcMMO.p.getGeneralConfig().getMySQLDatabaseName()); + statement.setString(2, tablePrefix + tableName); + } else { + // For H2, the schema parameter is not needed + statement.setString(1, tablePrefix + tableName); + } + } + + Connection getConnection(PoolIdentifier identifier) throws SQLException { + Connection connection = switch (identifier) { + case LOAD -> loadPool.getConnection(); + case MISC -> miscPool.getConnection(); + case SAVE -> savePool.getConnection(); + }; if (connection == null) { throw new RuntimeException("getConnection() for " + identifier.name().toLowerCase(Locale.ENGLISH) + " pool timed out. Increase max connections settings."); } @@ -1012,8 +1080,9 @@ public final class SQLDatabaseManager implements DatabaseManager { * @param upgrade Upgrade to attempt to apply */ private void checkDatabaseStructure(Connection connection, UpgradeType upgrade) { + // TODO: Rewrite / Refactor if (!mcMMO.getUpgradeManager().shouldUpgrade(upgrade)) { - LogUtils.debug(mcMMO.p.getLogger(), "Skipping " + upgrade.name() + " upgrade (unneeded)"); + LogUtils.debug(logger, "Skipping " + upgrade.name() + " upgrade (unneeded)"); return; } @@ -1132,9 +1201,9 @@ public final class SQLDatabaseManager implements DatabaseManager { final int OFFSET_SKILLS = 0; // TODO update these numbers when the query // changes (a new skill is added) - final int OFFSET_XP = 13; - final int OFFSET_DATS = 26; - final int OFFSET_OTHER = 39; + final int OFFSET_XP = 15; + final int OFFSET_DATS = 28; + final int OFFSET_OTHER = 41; skills.put(PrimarySkillType.TAMING, result.getInt(OFFSET_SKILLS + 1)); skills.put(PrimarySkillType.MINING, result.getInt(OFFSET_SKILLS + 2)); @@ -1149,6 +1218,8 @@ public final class SQLDatabaseManager implements DatabaseManager { skills.put(PrimarySkillType.ACROBATICS, result.getInt(OFFSET_SKILLS + 11)); skills.put(PrimarySkillType.FISHING, result.getInt(OFFSET_SKILLS + 12)); skills.put(PrimarySkillType.ALCHEMY, result.getInt(OFFSET_SKILLS + 13)); + skills.put(PrimarySkillType.CROSSBOWS, result.getInt(OFFSET_SKILLS + 14)); + skills.put(PrimarySkillType.TRIDENTS, result.getInt(OFFSET_SKILLS + 15)); skillsXp.put(PrimarySkillType.TAMING, result.getFloat(OFFSET_XP + 1)); skillsXp.put(PrimarySkillType.MINING, result.getFloat(OFFSET_XP + 2)); @@ -1163,6 +1234,8 @@ public final class SQLDatabaseManager implements DatabaseManager { skillsXp.put(PrimarySkillType.ACROBATICS, result.getFloat(OFFSET_XP + 11)); skillsXp.put(PrimarySkillType.FISHING, result.getFloat(OFFSET_XP + 12)); skillsXp.put(PrimarySkillType.ALCHEMY, result.getFloat(OFFSET_XP + 13)); + skillsXp.put(PrimarySkillType.CROSSBOWS, result.getFloat(OFFSET_XP + 14)); + skillsXp.put(PrimarySkillType.TRIDENTS, result.getFloat(OFFSET_XP + 15)); // Taming - Unused - result.getInt(OFFSET_DATS + 1) skillsDATS.put(SuperAbilityType.SUPER_BREAKER, result.getInt(OFFSET_DATS + 2)); @@ -1177,6 +1250,8 @@ public final class SQLDatabaseManager implements DatabaseManager { // Acrobatics - Unused - result.getInt(OFFSET_DATS + 11) skillsDATS.put(SuperAbilityType.BLAST_MINING, result.getInt(OFFSET_DATS + 12)); uniqueData.put(UniqueDataType.CHIMAERA_WING_DATS, result.getInt(OFFSET_DATS + 13)); + skillsDATS.put(SuperAbilityType.SUPER_SHOTGUN, result.getInt(OFFSET_DATS + 14)); + skillsDATS.put(SuperAbilityType.TRIDENTS_SUPER_ABILITY, result.getInt(OFFSET_DATS + 15)); try { scoreboardTipsShown = result.getInt(OFFSET_OTHER + 2); @@ -1196,17 +1271,21 @@ public final class SQLDatabaseManager implements DatabaseManager { } private void printErrors(SQLException ex) { - if (debug) { - ex.printStackTrace(); - } + ex.printStackTrace(); - StackTraceElement element = ex.getStackTrace()[0]; - mcMMO.p.getLogger().severe("Location: " + element.getClassName() + " " + element.getMethodName() + " " + element.getLineNumber()); - mcMMO.p.getLogger().severe("SQLException: " + ex.getMessage()); - mcMMO.p.getLogger().severe("SQLState: " + ex.getSQLState()); - mcMMO.p.getLogger().severe("VendorError: " + ex.getErrorCode()); + // logger.severe("SQLException: " + ex.getMessage()); + logger.severe("SQLState: " + ex.getSQLState()); + logger.severe("VendorError: " + ex.getErrorCode()); + + // Handling SQLException chain + SQLException nextException = ex.getNextException(); + while (nextException != null) { + logger.severe("Caused by: " + nextException.getMessage()); + nextException = nextException.getNextException(); + } } + public DatabaseType getDatabaseType() { return DatabaseType.SQL; } @@ -1222,7 +1301,7 @@ public final class SQLDatabaseManager implements DatabaseManager { return; } resultSet.close(); - mcMMO.p.getLogger().info("Updating mcMMO MySQL tables to drop name uniqueness..."); + logger.info("Updating mcMMO MySQL tables to drop name uniqueness..."); statement.execute("ALTER TABLE `" + tablePrefix + "users` " + "DROP INDEX `user`," + "ADD INDEX `user` (`user`(20) ASC)"); @@ -1240,7 +1319,7 @@ public final class SQLDatabaseManager implements DatabaseManager { mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_ALCHEMY); } catch (SQLException ex) { - mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Alchemy..."); + logger.info("Updating mcMMO MySQL tables for Alchemy..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD `alchemy` int(10) NOT NULL DEFAULT '0'"); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "experience` ADD `alchemy` int(10) NOT NULL DEFAULT '0'"); } @@ -1252,7 +1331,7 @@ public final class SQLDatabaseManager implements DatabaseManager { mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_BLAST_MINING_COOLDOWN); } catch (SQLException ex) { - mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Blast Mining..."); + logger.info("Updating mcMMO MySQL tables for Blast Mining..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "cooldowns` ADD `blast_mining` int(32) NOT NULL DEFAULT '0'"); } } @@ -1263,7 +1342,7 @@ public final class SQLDatabaseManager implements DatabaseManager { mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_UNIQUE_PLAYER_DATA); } catch (SQLException ex) { - mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Chimaera Wing..."); + logger.info("Updating mcMMO MySQL tables for Chimaera Wing..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "cooldowns` ADD `chimaera_wing` int(32) NOT NULL DEFAULT '0'"); } } @@ -1274,7 +1353,7 @@ public final class SQLDatabaseManager implements DatabaseManager { mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_FISHING); } catch (SQLException ex) { - mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for Fishing..."); + logger.info("Updating mcMMO MySQL tables for Fishing..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD `fishing` int(10) NOT NULL DEFAULT '0'"); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "experience` ADD `fishing` int(10) NOT NULL DEFAULT '0'"); } @@ -1286,7 +1365,7 @@ public final class SQLDatabaseManager implements DatabaseManager { mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_MOB_HEALTHBARS); } catch (SQLException ex) { - mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for mob healthbars..."); + logger.info("Updating mcMMO MySQL tables for mob healthbars..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "huds` ADD `mobhealthbar` varchar(50) NOT NULL DEFAULT '" + mcMMO.p.getGeneralConfig().getMobHealthbarDefault() + "'"); } } @@ -1297,7 +1376,7 @@ public final class SQLDatabaseManager implements DatabaseManager { mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.ADD_SCOREBOARD_TIPS); } catch (SQLException ex) { - mcMMO.p.getLogger().info("Updating mcMMO MySQL tables for scoreboard tips..."); + logger.info("Updating mcMMO MySQL tables for scoreboard tips..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "huds` ADD `scoreboardtips` int(10) NOT NULL DEFAULT '0' ;"); } } @@ -1310,7 +1389,7 @@ public final class SQLDatabaseManager implements DatabaseManager { resultSet.last(); if (resultSet.getRow() != SkillTools.NON_CHILD_SKILLS.size()) { - mcMMO.p.getLogger().info("Indexing tables, this may take a while on larger databases"); + logger.info("Indexing tables, this may take a while on larger databases"); for (PrimarySkillType skill : SkillTools.NON_CHILD_SKILLS) { String skill_name = skill.name().toLowerCase(Locale.ENGLISH); @@ -1351,7 +1430,7 @@ public final class SQLDatabaseManager implements DatabaseManager { } if (!column_exists) { - mcMMO.p.getLogger().info("Adding UUIDs to mcMMO MySQL user table..."); + logger.info("Adding UUIDs to mcMMO MySQL user table..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` ADD `uuid` varchar(36) NULL DEFAULT NULL"); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` ADD UNIQUE INDEX `uuid` (`uuid`) USING BTREE"); @@ -1420,7 +1499,7 @@ public final class SQLDatabaseManager implements DatabaseManager { } if (column_exists) { - mcMMO.p.getLogger().info("Removing party name from users table..."); + logger.info("Removing party name from users table..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "users` DROP COLUMN `party`"); } @@ -1454,7 +1533,7 @@ public final class SQLDatabaseManager implements DatabaseManager { } if (!column_exists) { - mcMMO.p.getLogger().info("Adding skill total column to skills table..."); + logger.info("Adding skill total column to skills table..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD COLUMN `total` int NOT NULL DEFAULT '0'"); statement.executeUpdate("UPDATE `" + tablePrefix + "skills` SET `total` = (taming+mining+woodcutting+repair+unarmed+herbalism+excavation+archery+swords+axes+acrobatics+fishing+alchemy)"); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "skills` ADD INDEX `idx_total` (`total`) USING BTREE"); @@ -1490,7 +1569,7 @@ public final class SQLDatabaseManager implements DatabaseManager { } if (column_exists) { - mcMMO.p.getLogger().info("Removing Spout HUD type from huds table..."); + logger.info("Removing Spout HUD type from huds table..."); statement.executeUpdate("ALTER TABLE `" + tablePrefix + "huds` DROP COLUMN `hudtype`"); } @@ -1515,7 +1594,7 @@ public final class SQLDatabaseManager implements DatabaseManager { PreparedStatement statement = null; try { - statement = connection.prepareStatement("SELECT id, user FROM " + tablePrefix + "users WHERE uuid = ? OR (uuid IS NULL AND user = ?)"); + statement = connection.prepareStatement("SELECT id, `user` FROM " + tablePrefix + "users WHERE uuid = ? OR (uuid IS NULL AND `user` = ?)"); statement.setString(1, uuid.toString()); statement.setString(2, playerName); resultSet = statement.executeQuery(); @@ -1544,7 +1623,7 @@ public final class SQLDatabaseManager implements DatabaseManager { PreparedStatement statement = null; try { - statement = connection.prepareStatement("SELECT id, user FROM " + tablePrefix + "users WHERE user = ?"); + statement = connection.prepareStatement("SELECT id, `user` FROM " + tablePrefix + "users WHERE `user` = ?"); statement.setString(1, playerName); resultSet = statement.executeQuery(); @@ -1577,7 +1656,7 @@ public final class SQLDatabaseManager implements DatabaseManager { @Override public void onDisable() { - LogUtils.debug(mcMMO.p.getLogger(), "Releasing connection pool resource..."); + LogUtils.debug(logger, "Releasing connection pool resource..."); miscPool.close(); loadPool.close(); savePool.close(); @@ -1619,19 +1698,19 @@ public final class SQLDatabaseManager implements DatabaseManager { */ //Alter users table - mcMMO.p.getLogger().info("SQL Converting tables from latin1 to utf8mb4"); + logger.info("SQL Converting tables from latin1 to utf8mb4"); //Update "user" column try { - mcMMO.p.getLogger().info("Updating user column to new encoding"); + logger.info("Updating user column to new encoding"); statement.executeUpdate(getUpdateUserInUsersTableSQLQuery()); //Update "uuid" column - mcMMO.p.getLogger().info("Updating user column to new encoding"); + logger.info("Updating user column to new encoding"); statement.executeUpdate(getUpdateUUIDInUsersTableSQLQuery()); //Update "mobhealthbar" column - mcMMO.p.getLogger().info("Updating mobhealthbar column to new encoding"); + logger.info("Updating mobhealthbar column to new encoding"); statement.executeUpdate(getUpdateMobHealthBarInHudsTableSQLQuery()); mcMMO.getUpgradeManager().setUpgradeCompleted(UpgradeType.SQL_CHARSET_UTF8MB4); @@ -1645,7 +1724,7 @@ public final class SQLDatabaseManager implements DatabaseManager { private String getUpdateUserInUsersTableSQLQuery() { return "ALTER TABLE\n" + " " + tablePrefix + "users\n" + - " CHANGE user user\n" + + " CHANGE `user` user\n" + " " + USER_VARCHAR + "\n" + " CHARACTER SET utf8mb4\n" + " COLLATE utf8mb4_unicode_ci;"; @@ -1670,4 +1749,28 @@ public final class SQLDatabaseManager implements DatabaseManager { " CHARACTER SET utf8mb4\n" + " COLLATE utf8mb4_unicode_ci;"; } + + public void printAllTablesWithColumns(Connection connection) { + try { + DatabaseMetaData metaData = connection.getMetaData(); + String[] types = {"TABLE"}; + ResultSet tables = metaData.getTables(null, null, "%", types); + + while (tables.next()) { + String tableName = tables.getString("TABLE_NAME"); + System.out.println("Table: " + tableName); + + ResultSet columns = metaData.getColumns(null, null, tableName, "%"); + while (columns.next()) { + String columnName = columns.getString("COLUMN_NAME"); + String columnType = columns.getString("TYPE_NAME"); + System.out.println(" Column: " + columnName + " Type: " + columnType); + } + columns.close(); + } + tables.close(); + } catch (SQLException e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/com/gmail/nossr50/database/flatfile/FlatFileDataUtil.java b/src/main/java/com/gmail/nossr50/database/flatfile/FlatFileDataUtil.java index da41a7acc..285c501fd 100644 --- a/src/main/java/com/gmail/nossr50/database/flatfile/FlatFileDataUtil.java +++ b/src/main/java/com/gmail/nossr50/database/flatfile/FlatFileDataUtil.java @@ -79,6 +79,8 @@ public class FlatFileDataUtil { case SKILLS_TAMING: case SKILLS_FISHING: case SKILLS_ALCHEMY: + case SKILLS_CROSSBOWS: + case SKILLS_TRIDENTS: return String.valueOf(startingLevel); case OVERHAUL_LAST_LOGIN: return String.valueOf(-1L); @@ -90,6 +92,9 @@ public class FlatFileDataUtil { case COOLDOWN_SKULL_SPLITTER: case COOLDOWN_SUPER_BREAKER: case COOLDOWN_BLAST_MINING: + case COOLDOWN_SUPER_SHOTGUN: + case COOLDOWN_TRIDENTS: + case COOLDOWN_ARCHERY: case SCOREBOARD_TIPS: case COOLDOWN_CHIMAERA_WING: case EXP_MINING: @@ -105,6 +110,8 @@ public class FlatFileDataUtil { case EXP_TAMING: case EXP_FISHING: case EXP_ALCHEMY: + case EXP_CROSSBOWS: + case EXP_TRIDENTS: return "0"; case UUID_INDEX: throw new IndexOutOfBoundsException(); //TODO: Add UUID recovery? Might not even be worth it. diff --git a/src/main/java/com/gmail/nossr50/datatypes/party/Party.java b/src/main/java/com/gmail/nossr50/datatypes/party/Party.java index 9b684c742..78ffb9966 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/party/Party.java +++ b/src/main/java/com/gmail/nossr50/datatypes/party/Party.java @@ -6,7 +6,6 @@ import com.gmail.nossr50.datatypes.experience.FormulaType; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.EventUtils; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.sounds.SoundManager; 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 80e53ccc7..91ec48fb4 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java @@ -20,7 +20,6 @@ import com.gmail.nossr50.datatypes.skills.ToolType; import com.gmail.nossr50.events.experience.McMMOPlayerPreXpGainEvent; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.party.ShareHandler; import com.gmail.nossr50.runnables.skills.AbilityDisableTask; import com.gmail.nossr50.runnables.skills.ToolLowerTask; @@ -29,7 +28,7 @@ import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager; import com.gmail.nossr50.skills.alchemy.AlchemyManager; import com.gmail.nossr50.skills.archery.ArcheryManager; import com.gmail.nossr50.skills.axes.AxesManager; -import com.gmail.nossr50.skills.child.FamilyTree; +import com.gmail.nossr50.skills.crossbows.CrossbowsManager; import com.gmail.nossr50.skills.excavation.ExcavationManager; import com.gmail.nossr50.skills.fishing.FishingManager; import com.gmail.nossr50.skills.herbalism.HerbalismManager; @@ -39,6 +38,7 @@ import com.gmail.nossr50.skills.salvage.SalvageManager; import com.gmail.nossr50.skills.smelting.SmeltingManager; 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.skills.woodcutting.WoodcuttingManager; import com.gmail.nossr50.util.*; @@ -68,7 +68,6 @@ import org.jetbrains.annotations.Nullable; import java.util.EnumMap; import java.util.Map; -import java.util.Set; import java.util.UUID; public class McMMOPlayer implements Identified { @@ -181,6 +180,9 @@ public class McMMOPlayer implements Identified { case AXES: skillManagers.put(primarySkillType, new AxesManager(this)); break; + case CROSSBOWS: + skillManagers.put(primarySkillType, new CrossbowsManager(this)); + break; case EXCAVATION: skillManagers.put(primarySkillType, new ExcavationManager(this)); break; @@ -208,6 +210,9 @@ public class McMMOPlayer implements Identified { case TAMING: skillManagers.put(primarySkillType, new TamingManager(this)); break; + case TRIDENTS: + skillManagers.put(primarySkillType, new TridentsManager(this)); + break; case UNARMED: skillManagers.put(primarySkillType, new UnarmedManager(this)); break; @@ -227,15 +232,6 @@ public class McMMOPlayer implements Identified { return attackStrength; } -// public void setAttackStrength(double attackStrength) { -// this.attackStrength = attackStrength; -// } - - /*public void hideXpBar(PrimarySkillType primarySkillType) - { - experienceBarManager.hideExperienceBar(primarySkillType); - }*/ - public @NotNull PrimarySkillType getLastSkillShownScoreboard() { return lastSkillShownScoreboard; } @@ -308,6 +304,13 @@ public class McMMOPlayer implements Identified { public AxesManager getAxesManager() { return (AxesManager) skillManagers.get(PrimarySkillType.AXES); } + public CrossbowsManager getCrossbowsManager() { + return (CrossbowsManager) skillManagers.get(PrimarySkillType.CROSSBOWS); + } + + public TridentsManager getTridentsManager() { + return (TridentsManager) skillManagers.get(PrimarySkillType.TRIDENTS); + } public ExcavationManager getExcavationManager() { return (ExcavationManager) skillManagers.get(PrimarySkillType.EXCAVATION); @@ -384,6 +387,7 @@ public class McMMOPlayer implements Identified { * @param isActive True if the ability is active, false otherwise */ public void setAbilityMode(SuperAbilityType ability, boolean isActive) { + // TODO: This should reject "one and done" type abilities abilityMode.put(ability, isActive); } @@ -611,7 +615,7 @@ public class McMMOPlayer implements Identified { } if (SkillTools.isChildSkill(skill)) { - Set parentSkills = FamilyTree.getParents(skill); + var parentSkills = mcMMO.p.getSkillTools().getChildSkillParents(skill); float splitXp = xp / parentSkills.size(); for (PrimarySkillType parentSkill : parentSkills) { @@ -668,7 +672,7 @@ public class McMMOPlayer implements Identified { xp = mcMMOPlayerPreXpGainEvent.getXpGained(); if (SkillTools.isChildSkill(primarySkillType)) { - Set parentSkills = FamilyTree.getParents(primarySkillType); + var parentSkills = mcMMO.p.getSkillTools().getChildSkillParents(primarySkillType); for (PrimarySkillType parentSkill : parentSkills) { applyXpGain(parentSkill, xp / parentSkills.size(), xpGainReason, xpGainSource); @@ -843,7 +847,7 @@ public class McMMOPlayer implements Identified { return 0; } - xp = (float) (xp / ExperienceConfig.getInstance().getFormulaSkillModifier(primarySkillType) * ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier()); + xp = (float) (xp * ExperienceConfig.getInstance().getFormulaSkillModifier(primarySkillType) * ExperienceConfig.getInstance().getExperienceGainsGlobalMultiplier()); if (mcMMO.p.getGeneralConfig().getToolModsEnabled()) { CustomTool tool = mcMMO.getModManager().getTool(player.getInventory().getItemInMainHand()); diff --git a/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java b/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java index 65ebf4c85..71abb6418 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java +++ b/src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java @@ -7,14 +7,17 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SuperAbilityType; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.runnables.player.PlayerProfileSaveTask; -import com.gmail.nossr50.skills.child.FamilyTree; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.skills.SkillTools; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.EnumMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import java.util.concurrent.DelayQueue; public class PlayerProfile { @@ -363,7 +366,7 @@ public class PlayerProfile { markProfileDirty(); if (SkillTools.isChildSkill(skill)) { - Set parentSkills = FamilyTree.getParents(skill); + var parentSkills = mcMMO.p.getSkillTools().getChildSkillParents(skill); float dividedXP = (xp / parentSkills.size()); for (PrimarySkillType parentSkill : parentSkills) { @@ -431,8 +434,12 @@ public class PlayerProfile { return mcMMO.getFormulaManager().getXPtoNextLevel(level, formulaType); } - private int getChildSkillLevel(PrimarySkillType primarySkillType) { - Set parents = FamilyTree.getParents(primarySkillType); + private int getChildSkillLevel(@NotNull PrimarySkillType primarySkillType) throws IllegalArgumentException { + if (!SkillTools.isChildSkill(primarySkillType)) { + throw new IllegalArgumentException(primarySkillType + " is not a child skill!"); + } + + ImmutableList parents = mcMMO.p.getSkillTools().getChildSkillParents(primarySkillType); int sum = 0; for (PrimarySkillType parent : parents) { diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/PrimarySkillType.java b/src/main/java/com/gmail/nossr50/datatypes/skills/PrimarySkillType.java index 5b513c280..e3e3dd575 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/skills/PrimarySkillType.java +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/PrimarySkillType.java @@ -16,6 +16,7 @@ public enum PrimarySkillType { ALCHEMY, ARCHERY, AXES, + CROSSBOWS, EXCAVATION, FISHING, HERBALISM, @@ -25,6 +26,7 @@ public enum PrimarySkillType { SMELTING, SWORDS, TAMING, + TRIDENTS, UNARMED, WOODCUTTING; diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillType.java b/src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillType.java index a11b98eb2..e6bcb079f 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillType.java +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillType.java @@ -31,6 +31,11 @@ public enum SubSkillType { AXES_GREATER_IMPACT(1), AXES_SKULL_SPLITTER(1), + /* CROSSBOWS */ + CROSSBOWS_CROSSBOWS_LIMIT_BREAK(10), + CROSSBOWS_TRICK_SHOT(3), + CROSSBOWS_POWERED_SHOT(20), + /* Excavation */ EXCAVATION_ARCHAEOLOGY(8), EXCAVATION_GIGA_DRILL_BREAKER(1), @@ -45,6 +50,7 @@ public enum SubSkillType { /* Herbalism */ HERBALISM_DOUBLE_DROPS(1), + HERBALISM_VERDANT_BOUNTY(1), HERBALISM_FARMERS_DIET(5), HERBALISM_GREEN_TERRA(1), HERBALISM_GREEN_THUMB(4), @@ -57,6 +63,7 @@ public enum SubSkillType { MINING_DEMOLITIONS_EXPERTISE(1), MINING_DOUBLE_DROPS(1), MINING_SUPER_BREAKER(1), + MINING_MOTHER_LODE(1), /* Repair */ REPAIR_ARCANE_FORGING(8), @@ -91,6 +98,10 @@ public enum SubSkillType { TAMING_SHOCK_PROOF(1), TAMING_THICK_FUR(1), + /* Tridents */ + TRIDENTS_IMPALE(10), + TRIDENTS_TRIDENTS_LIMIT_BREAK(10), + /* Unarmed */ UNARMED_ARROW_DEFLECT(1), UNARMED_BERSERK(1), @@ -101,13 +112,11 @@ public enum SubSkillType { UNARMED_UNARMED_LIMIT_BREAK(10), /* Woodcutting */ -/* WOODCUTTING_BARK_SURGEON(3),*/ WOODCUTTING_KNOCK_ON_WOOD(2), WOODCUTTING_HARVEST_LUMBER(1), WOODCUTTING_LEAF_BLOWER(1), -/* WOODCUTTING_NATURES_BOUNTY(3), - WOODCUTTING_SPLINTER(3),*/ - WOODCUTTING_TREE_FELLER(1); + WOODCUTTING_TREE_FELLER(1), + WOODCUTTING_CLEAN_CUTS(1); private final int numRanks; //TODO: SuperAbilityType should also contain flags for active by default? Not sure if it should work that way. @@ -134,7 +143,7 @@ public enum SubSkillType { /** * !!! This relies on the immutable lists in PrimarySkillType being populated !!! * If we add skills, those immutable lists need to be updated - * @return + * @return the parent skill of this subskill */ public PrimarySkillType getParentSkill() { return mcMMO.p.getSkillTools().getPrimarySkillBySubSkill(this); } diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/SuperAbilityType.java b/src/main/java/com/gmail/nossr50/datatypes/skills/SuperAbilityType.java index 6e4de8d78..f876ce53e 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/skills/SuperAbilityType.java +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/SuperAbilityType.java @@ -10,6 +10,12 @@ import org.bukkit.block.BlockState; import org.bukkit.entity.Player; public enum SuperAbilityType { + EXPLOSIVE_SHOT("Archery.Skills.ExplosiveShot.On", + "Archery.Skills.ExplosiveShot.Off", + "Archery.Skills.ExplosiveShot.Other.On", + "Archery.Skills.ExplosiveShot.Refresh", + "Archery.Skills.ExplosiveShot.Other.Off", + "Archery.SubSkill.ExplosiveShot.Name"), BERSERK( "Unarmed.Skills.Berserk.On", "Unarmed.Skills.Berserk.Off", @@ -65,6 +71,20 @@ public enum SuperAbilityType { "Swords.Skills.SS.Refresh", "Swords.Skills.SS.Other.Off", "Swords.SubSkill.SerratedStrikes.Name"), + SUPER_SHOTGUN( + null, + null, + "Crossbows.Skills.SSG.Other.On", + "Crossbows.Skills.SSG.Refresh", + null, + "Crossbows.SubSkill.SuperShotgun.Name"), + TRIDENTS_SUPER_ABILITY( + "Tridents.Skills.TA.On", + "Tridents.Skills.TA.Off", + "Tridents.Skills.TA.Other.On", + "Tridents.Skills.TA.Refresh", + "Tridents.Skills.TA.Other.Off", + "Tridents.SubSkill.TridentAbility.Name"), /** * Has cooldown - but has to share a skill with Super Breaker, so needs special treatment @@ -82,6 +102,7 @@ public enum SuperAbilityType { * Defining their associated SubSkillType definitions * This is a bit of a band-aid fix until the new skill system is in place */ + // TODO: This is stupid static { BERSERK.subSkillTypeDefinition = SubSkillType.UNARMED_BERSERK; SUPER_BREAKER.subSkillTypeDefinition = SubSkillType.MINING_SUPER_BREAKER; @@ -173,35 +194,22 @@ public enum SuperAbilityType { * @param player Player to check permissions for * @return true if the player has permissions, false otherwise */ + // TODO: Add unit tests + // TODO: This is stupid public boolean getPermissions(Player player) { - switch (this) { - case BERSERK: - return Permissions.berserk(player); - - case BLAST_MINING: - return Permissions.remoteDetonation(player); - - case GIGA_DRILL_BREAKER: - return Permissions.gigaDrillBreaker(player); - - case GREEN_TERRA: - return Permissions.greenTerra(player); - - case SERRATED_STRIKES: - return Permissions.serratedStrikes(player); - - case SKULL_SPLITTER: - return Permissions.skullSplitter(player); - - case SUPER_BREAKER: - return Permissions.superBreaker(player); - - case TREE_FELLER: - return Permissions.treeFeller(player); - - default: - return false; - } + return switch (this) { + case BERSERK -> Permissions.berserk(player); + case EXPLOSIVE_SHOT -> Permissions.explosiveShot(player); + case BLAST_MINING -> Permissions.remoteDetonation(player); + case GIGA_DRILL_BREAKER -> Permissions.gigaDrillBreaker(player); + case GREEN_TERRA -> Permissions.greenTerra(player); + case SERRATED_STRIKES -> Permissions.serratedStrikes(player); + case SKULL_SPLITTER -> Permissions.skullSplitter(player); + case SUPER_BREAKER -> Permissions.superBreaker(player); + case SUPER_SHOTGUN -> Permissions.superShotgun(player); + case TREE_FELLER -> Permissions.treeFeller(player); + case TRIDENTS_SUPER_ABILITY -> Permissions.tridentsSuper(player); + }; } /** @@ -211,25 +219,15 @@ public enum SuperAbilityType { * @return true if the block is affected by this ability, false otherwise */ public boolean blockCheck(BlockState blockState) { - switch (this) { - case BERSERK: - return (BlockUtils.affectedByGigaDrillBreaker(blockState) || blockState.getType() == Material.SNOW || mcMMO.getMaterialMapStore().isGlass(blockState.getType())); - - case GIGA_DRILL_BREAKER: - return BlockUtils.affectedByGigaDrillBreaker(blockState); - - case GREEN_TERRA: - return BlockUtils.canMakeMossy(blockState); - - case SUPER_BREAKER: - return BlockUtils.affectedBySuperBreaker(blockState); - - case TREE_FELLER: - return BlockUtils.hasWoodcuttingXP(blockState); - - default: - return false; - } + return switch (this) { + case BERSERK -> + (BlockUtils.affectedByGigaDrillBreaker(blockState) || blockState.getType() == Material.SNOW || mcMMO.getMaterialMapStore().isGlass(blockState.getType())); + case GIGA_DRILL_BREAKER -> BlockUtils.affectedByGigaDrillBreaker(blockState); + case GREEN_TERRA -> BlockUtils.canMakeMossy(blockState); + case SUPER_BREAKER -> BlockUtils.affectedBySuperBreaker(blockState); + case TREE_FELLER -> BlockUtils.hasWoodcuttingXP(blockState); + default -> false; + }; } /** diff --git a/src/main/java/com/gmail/nossr50/datatypes/skills/ToolType.java b/src/main/java/com/gmail/nossr50/datatypes/skills/ToolType.java index 9fdb444e9..dd61867d5 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/skills/ToolType.java +++ b/src/main/java/com/gmail/nossr50/datatypes/skills/ToolType.java @@ -10,7 +10,10 @@ public enum ToolType { HOE("Herbalism.Ability.Lower", "Herbalism.Ability.Ready"), PICKAXE("Mining.Ability.Lower", "Mining.Ability.Ready"), SHOVEL("Excavation.Ability.Lower", "Excavation.Ability.Ready"), - SWORD("Swords.Ability.Lower", "Swords.Ability.Ready"); + SWORD("Swords.Ability.Lower", "Swords.Ability.Ready"), + CROSSBOW("Crossbows.Ability.Lower", "Crossbows.Ability.Ready"), + BOW("Archery.Ability.Lower", "Archery.Ability.Ready"), + TRIDENTS("Tridents.Ability.Lower", "Tridents.Ability.Ready"); private final String lowerTool; private final String raiseTool; @@ -38,6 +41,10 @@ public enum ToolType { switch (this) { case AXE: return ItemUtils.isAxe(itemStack); + case CROSSBOW: + return ItemUtils.isCrossbow(itemStack); + case TRIDENTS: + return ItemUtils.isTrident(itemStack); case FISTS: return itemStack.getType() == Material.AIR; 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 64a3a14fb..15ea49679 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 @@ -14,11 +14,10 @@ import com.gmail.nossr50.util.ItemUtils; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; -import com.gmail.nossr50.util.random.RandomChanceSkill; -import com.gmail.nossr50.util.random.RandomChanceUtil; +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.SkillActivationType; import com.gmail.nossr50.util.skills.SkillUtils; import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; @@ -33,6 +32,8 @@ import org.bukkit.event.Event; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageEvent; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.VisibleForTesting; import java.util.Locale; @@ -110,7 +111,7 @@ public class Roll extends AcrobaticsSubSkill { */ @Override public boolean hasPermission(Player player) { - return Permissions.isSubSkillEnabled(player, this); + return Permissions.isSubSkillEnabled(player, getSubSkillType()); } /** @@ -128,14 +129,16 @@ public class Roll extends AcrobaticsSubSkill { float skillValue = playerProfile.getSkillLevel(getPrimarySkill()); boolean isLucky = Permissions.lucky(player, getPrimarySkill()); - String[] rollStrings = RandomChanceUtil.calculateAbilityDisplayValues(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.ACROBATICS_ROLL); + String[] rollStrings = ProbabilityUtil.getRNGDisplayValues(player, SubSkillType.ACROBATICS_ROLL); rollChance = rollStrings[0]; rollChanceLucky = rollStrings[1]; /* * Graceful is double the odds of a normal roll */ - String[] gracefulRollStrings = RandomChanceUtil.calculateAbilityDisplayValuesCustom(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, player, SubSkillType.ACROBATICS_ROLL, 2.0D); + Probability probability = getRollProbability(player); + Probability gracefulProbability = Probability.ofPercent(probability.getValue() * 2); + String[] gracefulRollStrings = ProbabilityUtil.getRNGDisplayValues(gracefulProbability); gracefulRollChance = gracefulRollStrings[0]; gracefulRollChanceLucky = gracefulRollStrings[1]; @@ -166,6 +169,11 @@ public class Roll extends AcrobaticsSubSkill { } + @NotNull + private Probability getRollProbability(Player player) { + return ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, player); + } + @Override public boolean isSuperAbility() { return false; @@ -191,7 +199,8 @@ public class Roll extends AcrobaticsSubSkill { * @param damage The amount of damage initially dealt by the event * @return the modified event damage if the ability was successful, the original event damage otherwise */ - private double rollCheck(Player player, McMMOPlayer mcMMOPlayer, double damage) { + @VisibleForTesting + public double rollCheck(Player player, McMMOPlayer mcMMOPlayer, double damage) { int skillLevel = mcMMOPlayer.getSkillLevel(getPrimarySkill()); @@ -202,7 +211,7 @@ public class Roll extends AcrobaticsSubSkill { double modifiedDamage = calculateModifiedRollDamage(damage, mcMMO.p.getAdvancedConfig().getRollDamageThreshold()); if (!isFatal(player, modifiedDamage) - && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ACROBATICS_ROLL, player)) { + && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ACROBATICS_ROLL, player)) { NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Roll.Text"); SoundManager.sendCategorizedSound(player, player.getLocation(), SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS); //player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Text")); @@ -239,11 +248,11 @@ public class Roll extends AcrobaticsSubSkill { private double gracefulRollCheck(Player player, McMMOPlayer mcMMOPlayer, double damage, int skillLevel) { double modifiedDamage = calculateModifiedRollDamage(damage, mcMMO.p.getAdvancedConfig().getRollDamageThreshold() * 2); - RandomChanceSkill rcs = new RandomChanceSkill(player, subSkillType); - rcs.setSkillLevel(rcs.getSkillLevel() * 2); //Double the effective odds + Probability gracefulProbability = getGracefulProbability(player); if (!isFatal(player, modifiedDamage) - && RandomChanceUtil.checkRandomChanceExecutionSuccess(rcs)) + //TODO: Graceful isn't sending out an event + && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.ACROBATICS, player, gracefulProbability)) { NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Ability.Proc"); SoundManager.sendCategorizedSound(player, player.getLocation(), SoundType.ROLL_ACTIVATED, SoundCategory.PLAYERS,0.5F); @@ -263,6 +272,12 @@ public class Roll extends AcrobaticsSubSkill { return damage; } + @NotNull + public static Probability getGracefulProbability(Player player) { + double gracefulOdds = ProbabilityUtil.getSubSkillProbability(SubSkillType.ACROBATICS_ROLL, player).getValue() * 2; + return Probability.ofPercent(gracefulOdds); + } + /** * Check if the player is "farming" Acrobatics XP using * exploits in the game. @@ -412,28 +427,21 @@ public class Roll extends AcrobaticsSubSkill { @Override public Double[] getStats(Player player) { - double playerChanceRoll, playerChanceGrace; + double playerChanceRoll = ProbabilityUtil.getSubSkillProbability(subSkillType, player).getValue(); + double playerChanceGrace = playerChanceRoll * 2; - RandomChanceSkill roll = new RandomChanceSkill(player, getSubSkillType()); - RandomChanceSkill graceful = new RandomChanceSkill(player, getSubSkillType()); - - graceful.setSkillLevel(graceful.getSkillLevel() * 2); //Double odds - - //Calculate - playerChanceRoll = RandomChanceUtil.getRandomChanceExecutionChance(roll); - playerChanceGrace = RandomChanceUtil.getRandomChanceExecutionChance(graceful); + double gracefulOdds = ProbabilityUtil.getSubSkillProbability(subSkillType, player).getValue() * 2; return new Double[]{ playerChanceRoll, playerChanceGrace }; } - public void addFallLocation(Player player) + public void addFallLocation(@NotNull Player player) { UserManager.getPlayer(player).getAcrobaticsManager().addLocationToFallMap(getBlockLocation(player)); } - public Location getBlockLocation(Player player) + public @NotNull Location getBlockLocation(@NotNull Player player) { return player.getLocation().getBlock().getLocation(); } - } diff --git a/src/main/java/com/gmail/nossr50/datatypes/treasure/Treasure.java b/src/main/java/com/gmail/nossr50/datatypes/treasure/Treasure.java index 013849de2..659a94fc1 100644 --- a/src/main/java/com/gmail/nossr50/datatypes/treasure/Treasure.java +++ b/src/main/java/com/gmail/nossr50/datatypes/treasure/Treasure.java @@ -1,25 +1,34 @@ package com.gmail.nossr50.datatypes.treasure; +import com.gmail.nossr50.util.random.Probability; +import com.google.common.base.Objects; import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; public abstract class Treasure { private int xp; private double dropChance; + private @NotNull Probability dropProbability; private int dropLevel; - private ItemStack drop; + private @NotNull ItemStack drop; public Treasure(ItemStack drop, int xp, double dropChance, int dropLevel) { this.drop = drop; this.xp = xp; this.dropChance = dropChance; + this.dropProbability = Probability.ofPercent(dropChance); this.dropLevel = dropLevel; } - public ItemStack getDrop() { + public @NotNull Probability getDropProbability() { + return dropProbability; + } + + public @NotNull ItemStack getDrop() { return drop; } - public void setDrop(ItemStack drop) { + public void setDrop(@NotNull ItemStack drop) { this.drop = drop; } @@ -35,8 +44,9 @@ public abstract class Treasure { return dropChance; } - public void setDropChance(Double dropChance) { + public void setDropChance(double dropChance) { this.dropChance = dropChance; + this.dropProbability = Probability.ofPercent(dropChance); } public int getDropLevel() { @@ -46,4 +56,28 @@ public abstract class Treasure { public void setDropLevel(int dropLevel) { this.dropLevel = dropLevel; } + + @Override + public String toString() { + return "Treasure{" + + "xp=" + xp + + ", dropChance=" + dropChance + + ", dropProbability=" + dropProbability + + ", dropLevel=" + dropLevel + + ", drop=" + drop + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Treasure treasure = (Treasure) o; + return xp == treasure.xp && Double.compare(treasure.dropChance, dropChance) == 0 && dropLevel == treasure.dropLevel && Objects.equal(dropProbability, treasure.dropProbability) && Objects.equal(drop, treasure.drop); + } + + @Override + public int hashCode() { + return Objects.hashCode(xp, dropChance, dropProbability, dropLevel, drop); + } } diff --git a/src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerSkillEvent.java b/src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerSkillEvent.java index e899ea528..6238886c0 100644 --- a/src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerSkillEvent.java +++ b/src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerSkillEvent.java @@ -11,10 +11,10 @@ import org.jetbrains.annotations.NotNull; * Generic event for mcMMO skill handling. */ public abstract class McMMOPlayerSkillEvent extends PlayerEvent { - protected PrimarySkillType skill; + protected @NotNull PrimarySkillType skill; protected int skillLevel; - protected McMMOPlayerSkillEvent(Player player, PrimarySkillType skill) { + protected McMMOPlayerSkillEvent(@NotNull Player player, @NotNull PrimarySkillType skill) { super(player); this.skill = skill; this.skillLevel = UserManager.getPlayer(player).getSkillLevel(skill); @@ -23,7 +23,7 @@ public abstract class McMMOPlayerSkillEvent extends PlayerEvent { /** * @return The skill involved in this event */ - public PrimarySkillType getSkill() { + public @NotNull PrimarySkillType getSkill() { return skill; } diff --git a/src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillRandomCheckEvent.java b/src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillRandomCheckEvent.java index c1a90a425..3756c6cdd 100644 --- a/src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillRandomCheckEvent.java +++ b/src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillRandomCheckEvent.java @@ -1,47 +1,41 @@ -package com.gmail.nossr50.events.skills.secondaryabilities; - -import com.gmail.nossr50.datatypes.skills.SubSkillType; -import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill; -import org.bukkit.entity.Player; - -public class SubSkillRandomCheckEvent extends SubSkillEvent { - private double chance; - - public SubSkillRandomCheckEvent(Player player, SubSkillType ability, double chance) { - super(player, ability); - this.chance = chance; - } - - public SubSkillRandomCheckEvent(Player player, AbstractSubSkill abstractSubSkill, double chance) - { - super(player, abstractSubSkill); - this.chance = chance; - } - - /** - * Gets the activation chance of the ability 0D being no chance, 100.0D being 100% chance - * - * @return The activation chance of the ability - */ - public double getChance() { - return chance; - } - - /** - * Sets the activation chance of the ability [0D-100.0D] - * - * @param chance The activation chance of the ability - */ - public void setChance(double chance) { - this.chance = chance; - } - - /** - * Sets the activation chance of the ability to 100% or 0% - * - * @param success whether it should be successful or not - */ - public void setSuccessful(boolean success) { - this.chance = success ? 100.0D : 0D; - } -} +//package com.gmail.nossr50.events.skills.secondaryabilities; +// +//import com.gmail.nossr50.datatypes.skills.SubSkillType; +//import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill; +//import org.bukkit.entity.Player; +// +//public class SubSkillRandomCheckEvent extends SubSkillEvent { +// private double chance; +// +// public SubSkillRandomCheckEvent(Player player, SubSkillType ability, double chance) { +// super(player, ability); +// this.chance = chance; +// } +// +// /** +// * Gets the activation chance of the ability 0D being no chance, 100.0D being 100% chance +// * +// * @return The activation chance of the ability +// */ +// public double getChance() { +// return chance; +// } +// +// /** +// * Sets the activation chance of the ability [0D-100.0D] +// * +// * @param chance The activation chance of the ability +// */ +// public void setChance(double chance) { +// this.chance = chance; +// } +// +// /** +// * Sets the activation chance of the ability to 100% or 0% +// * +// * @param success whether it should be successful or not +// */ +// public void setSuccessful(boolean success) { +// this.chance = success ? 100.0D : 0D; +// } +//} diff --git a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java index c49c759c9..63585b917 100644 --- a/src/main/java/com/gmail/nossr50/listeners/BlockListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/BlockListener.java @@ -417,7 +417,7 @@ public class BlockListener implements Listener { woodcuttingManager.processWoodcuttingBlockXP(blockState); //Check for bonus drops - woodcuttingManager.processHarvestLumber(blockState); + woodcuttingManager.processBonusDropCheck(blockState); } } diff --git a/src/main/java/com/gmail/nossr50/listeners/CommandListener.java b/src/main/java/com/gmail/nossr50/listeners/CommandListener.java deleted file mode 100644 index 484faa5bb..000000000 --- a/src/main/java/com/gmail/nossr50/listeners/CommandListener.java +++ /dev/null @@ -1,40 +0,0 @@ -//package com.gmail.nossr50.listeners; -// -//import com.gmail.nossr50.datatypes.player.McMMOPlayer; -//import com.gmail.nossr50.datatypes.skills.SuperAbilityType; -//import com.gmail.nossr50.mcMMO; -//import com.gmail.nossr50.util.player.UserManager; -//import com.gmail.nossr50.util.skills.SkillUtils; -//import org.bukkit.Bukkit; -//import org.bukkit.entity.Player; -//import org.bukkit.event.EventHandler; -//import org.bukkit.event.EventPriority; -//import org.bukkit.event.Listener; -//import org.bukkit.event.player.PlayerCommandPreprocessEvent; -// -//public class CommandListener implements Listener { -// -// private final mcMMO pluginRef; -// -// public CommandListener(mcMMO plugin) { -// this.pluginRef = plugin; -// } -// -// @EventHandler(ignoreCancelled = true, priority = EventPriority.LOWEST) -// public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) { -// Player player = event.getPlayer(); -// -// SkillUtils.removeAbilityBoostsFromInventory(player); -// -// McMMOPlayer mmoPlayer = UserManager.getPlayer(player); -// -// if(mmoPlayer == null) -// return; -// -// Bukkit.getServer().getScheduler().runTaskLater(pluginRef, () -> { -// if(mmoPlayer.getAbilityMode(SuperAbilityType.GIGA_DRILL_BREAKER) || mmoPlayer.getAbilityMode(SuperAbilityType.SUPER_BREAKER)) { -// SkillUtils.handleAbilitySpeedIncrease(player); -// } -// }, 5); -// } -//} diff --git a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java index c762e9436..18edee783 100644 --- a/src/main/java/com/gmail/nossr50/listeners/EntityListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/EntityListener.java @@ -11,6 +11,7 @@ import com.gmail.nossr50.metadata.MobMetaFlagType; import com.gmail.nossr50.metadata.MobMetadataService; import com.gmail.nossr50.runnables.TravelingBlockMetaCleanup; import com.gmail.nossr50.skills.archery.Archery; +import com.gmail.nossr50.skills.crossbows.Crossbows; import com.gmail.nossr50.skills.mining.BlastMining; import com.gmail.nossr50.skills.mining.MiningManager; import com.gmail.nossr50.skills.taming.Taming; @@ -19,9 +20,8 @@ import com.gmail.nossr50.skills.unarmed.UnarmedManager; import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.worldguard.WorldGuardManager; import com.gmail.nossr50.worldguard.WorldGuardUtils; import org.bukkit.ChatColor; @@ -62,30 +62,6 @@ public class EntityListener implements Listener { mobMetadataService = mcMMO.getMetadataService().getMobMetadataService(); } -// @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) -// public void onBlockDropItemEvent(EntityDropItemEvent event) { -// if(event.getEntity() instanceof Block) { -// Block itemDispensingBlock = (Block) event.getEntity(); -// -// //Is it a berry bush? -// if(itemDispensingBlock.getType().toString().equalsIgnoreCase("sweet_berry_bush")) { -// //Berry Bush Time! -// if (event.getEntity().getMetadata(mcMMO.BONUS_DROPS_METAKEY).size() > 0) { -// Bukkit.broadcastMessage("Pop pop!"); -// BonusDropMeta bonusDropMeta = (BonusDropMeta) event.getEntity().getMetadata(mcMMO.BONUS_DROPS_METAKEY).get(0); -// int bonusCount = bonusDropMeta.asInt(); -// -// for (int i = 0; i < bonusCount; i++) { -// Misc.spawnItemNaturally(event.getEntity().getLocation(), event.getItemDrop().getItemStack(), ItemSpawnReason.BONUS_DROPS); -// } -// } -// } -// -// if(event.getEntity().hasMetadata(mcMMO.BONUS_DROPS_METAKEY)) -// event.getEntity().removeMetadata(mcMMO.BONUS_DROPS_METAKEY, pluginRef); -// } -// } - @EventHandler(priority = EventPriority.MONITOR) public void onEntityTransform(EntityTransformEvent event) { if(event.getEntity() instanceof LivingEntity livingEntity) { @@ -124,7 +100,7 @@ public class EntityListener implements Listener { } } - @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = false) public void onEntityShootBow(EntityShootBowEvent event) { /* WORLD BLACKLIST CHECK */ if(WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) @@ -132,32 +108,28 @@ public class EntityListener implements Listener { if(event.getEntity() instanceof Player player) { - - /* WORLD GUARD MAIN FLAG CHECK */ - if(WorldGuardUtils.isWorldGuardLoaded()) - { - if(!WorldGuardManager.getInstance().hasMainFlag(player)) - return; - } - Entity projectile = event.getProjectile(); //Should be noted that there are API changes regarding Arrow from 1.13.2 to current versions of the game - if (!(projectile instanceof Arrow)) { + if (!(projectile instanceof Arrow arrow)) { return; } ItemStack bow = event.getBow(); - if (bow != null - && bow.containsEnchantment(Enchantment.ARROW_INFINITE)) { + if (bow == null) + return; + + if (bow.containsEnchantment(Enchantment.ARROW_INFINITE)) { projectile.setMetadata(MetadataConstants.METADATA_KEY_INF_ARROW, MetadataConstants.MCMMO_METADATA_VALUE); } + // Set BowType, Force, and Distance metadata projectile.setMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, new FixedMetadataValue(pluginRef, Math.min(event.getForce() * mcMMO.p.getAdvancedConfig().getForceMultiplier(), 1.0))); - projectile.setMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, new FixedMetadataValue(pluginRef, projectile.getLocation())); + projectile.setMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, new FixedMetadataValue(pluginRef, arrow.getLocation())); + //Cleanup metadata in 1 minute in case normal collection falls through - CombatUtils.delayArrowMetaCleanup((Projectile) projectile); + CombatUtils.delayArrowMetaCleanup(arrow); } } @@ -176,25 +148,28 @@ public class EntityListener implements Listener { return; } - Projectile projectile = event.getEntity(); - EntityType entityType = projectile.getType(); + if(event.getEntity() instanceof Arrow arrow) { + // Delayed metadata cleanup in case other cleanup hooks fail + CombatUtils.delayArrowMetaCleanup(arrow); - if(entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) { - CombatUtils.delayArrowMetaCleanup(projectile); //Cleans up metadata 1 minute from now in case other collection methods fall through + // If fired from an item with multi-shot, we need to track + if(ItemUtils.doesPlayerHaveEnchantmentInHands(player, "multishot")) { + arrow.setMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW, MetadataConstants.MCMMO_METADATA_VALUE); + } - if(!projectile.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) - projectile.setMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, new FixedMetadataValue(pluginRef, 1.0)); + if(!arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) + arrow.setMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, new FixedMetadataValue(pluginRef, 1.0)); - if(!projectile.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) - projectile.setMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, new FixedMetadataValue(pluginRef, projectile.getLocation())); + if(!arrow.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) + arrow.setMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, new FixedMetadataValue(pluginRef, arrow.getLocation())); //Check both hands if(ItemUtils.doesPlayerHaveEnchantmentInHands(player, "piercing")) { return; } - if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ARCHERY_ARROW_RETRIEVAL, player)) { - projectile.setMetadata(MetadataConstants.METADATA_KEY_TRACKED_ARROW, MetadataConstants.MCMMO_METADATA_VALUE); + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ARCHERY_ARROW_RETRIEVAL, player)) { + arrow.setMetadata(MetadataConstants.METADATA_KEY_TRACKED_ARROW, MetadataConstants.MCMMO_METADATA_VALUE); } } } @@ -424,8 +399,8 @@ public class EntityListener implements Listener { } } - if(entityDamageEvent.getDamager() instanceof Projectile) { - CombatUtils.cleanupArrowMetadata((Projectile) entityDamageEvent.getDamager()); + if(entityDamageEvent.getDamager() instanceof Arrow arrow) { + CombatUtils.delayArrowMetaCleanup(arrow); } if(entityDamageEvent.getEntity() instanceof Player player && entityDamageEvent.getDamager() instanceof Player) { @@ -1115,5 +1090,16 @@ public class EntityListener implements Listener { } } + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onProjectileHitEvent(ProjectileHitEvent event) { + /* WORLD BLACKLIST CHECK */ + if (WorldBlacklist.isWorldBlacklisted(event.getEntity().getWorld())) + return; + if(event.getEntity() instanceof Arrow arrow) { + if(arrow.isShotFromCrossbow()) { + Crossbows.processCrossbows(event, pluginRef, arrow); + } + } + } } diff --git a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java index a63b7650e..ab1526382 100644 --- a/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java +++ b/src/main/java/com/gmail/nossr50/listeners/PlayerListener.java @@ -787,6 +787,9 @@ public class PlayerListener implements Listener { } McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); + if (mcMMOPlayer == null) + return; + ItemStack heldItem = player.getInventory().getItemInMainHand(); //Spam Fishing Detection diff --git a/src/main/java/com/gmail/nossr50/mcMMO.java b/src/main/java/com/gmail/nossr50/mcMMO.java index 700699140..d76efa993 100644 --- a/src/main/java/com/gmail/nossr50/mcMMO.java +++ b/src/main/java/com/gmail/nossr50/mcMMO.java @@ -31,7 +31,6 @@ import com.gmail.nossr50.runnables.player.ClearRegisteredXPGainTask; import com.gmail.nossr50.runnables.player.PlayerProfileLoadingTask; import com.gmail.nossr50.runnables.player.PowerLevelUpdatingTask; import com.gmail.nossr50.skills.alchemy.Alchemy; -import com.gmail.nossr50.skills.child.ChildConfig; import com.gmail.nossr50.skills.repair.repairables.Repairable; import com.gmail.nossr50.skills.repair.repairables.RepairableManager; import com.gmail.nossr50.skills.repair.repairables.SimpleRepairableManager; @@ -99,7 +98,7 @@ public class mcMMO extends JavaPlugin { private static CommandManager commandManager; //ACF private static TransientEntityTracker transientEntityTracker; - private @NotNull SkillTools skillTools; + private SkillTools skillTools; private static boolean serverShutdownExecuted = false; @@ -571,8 +570,6 @@ public class mcMMO extends JavaPlugin { SoundConfig.getInstance(); RankConfig.getInstance(); - new ChildConfig(); - List repairables = new ArrayList<>(); if (generalConfig.getToolModsEnabled()) { diff --git a/src/main/java/com/gmail/nossr50/party/PartyManager.java b/src/main/java/com/gmail/nossr50/party/PartyManager.java index 130a9b996..beff259d2 100644 --- a/src/main/java/com/gmail/nossr50/party/PartyManager.java +++ b/src/main/java/com/gmail/nossr50/party/PartyManager.java @@ -352,9 +352,6 @@ public final class PartyManager { * @param password The password for this party, null if there was no password */ public void createParty(@NotNull McMMOPlayer mcMMOPlayer, @NotNull String partyName, @Nullable String password) { - requireNonNull(mcMMOPlayer, "mcMMOPlayer cannot be null!"); - requireNonNull(partyName, "partyName cannot be null!"); - Player player = mcMMOPlayer.getPlayer(); Party party = new Party(new PartyLeader(player.getUniqueId(), player.getName()), partyName.replace(".", ""), password); diff --git a/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java b/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java index d9822e191..613f3ecee 100644 --- a/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/SaveTimerTask.java @@ -2,7 +2,6 @@ package com.gmail.nossr50.runnables; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.runnables.player.PlayerProfileSaveTask; import com.gmail.nossr50.util.CancellableRunnable; import com.gmail.nossr50.util.LogUtils; diff --git a/src/main/java/com/gmail/nossr50/runnables/items/TeleportationWarmup.java b/src/main/java/com/gmail/nossr50/runnables/items/TeleportationWarmup.java index 7490c4117..25d809f59 100644 --- a/src/main/java/com/gmail/nossr50/runnables/items/TeleportationWarmup.java +++ b/src/main/java/com/gmail/nossr50/runnables/items/TeleportationWarmup.java @@ -3,7 +3,6 @@ package com.gmail.nossr50.runnables.items; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.CancellableRunnable; import com.gmail.nossr50.util.EventUtils; import com.gmail.nossr50.util.Misc; diff --git a/src/main/java/com/gmail/nossr50/runnables/party/PartyAutoKickTask.java b/src/main/java/com/gmail/nossr50/runnables/party/PartyAutoKickTask.java index 2db0c3c27..1d662dba9 100644 --- a/src/main/java/com/gmail/nossr50/runnables/party/PartyAutoKickTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/party/PartyAutoKickTask.java @@ -2,7 +2,6 @@ package com.gmail.nossr50.runnables.party; import com.gmail.nossr50.datatypes.party.Party; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.party.PartyManager; import com.gmail.nossr50.util.CancellableRunnable; import org.bukkit.OfflinePlayer; diff --git a/src/main/java/com/gmail/nossr50/runnables/skills/AbilityCooldownTask.java b/src/main/java/com/gmail/nossr50/runnables/skills/AbilityCooldownTask.java index 69e080972..9c7d8bfd8 100644 --- a/src/main/java/com/gmail/nossr50/runnables/skills/AbilityCooldownTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/skills/AbilityCooldownTask.java @@ -21,9 +21,7 @@ public class AbilityCooldownTask extends CancellableRunnable { return; } - mcMMOPlayer.setAbilityInformed(ability, true); - + mcMMOPlayer.setAbilityInformed(ability, true); // TODO: ?? What does this do again? NotificationManager.sendPlayerInformation(mcMMOPlayer.getPlayer(), NotificationType.ABILITY_REFRESHED, ability.getAbilityRefresh()); - //mcMMOPlayer.getPlayer().sendMessage(ability.getAbilityRefresh()); } } 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 f2e9c608f..432d5a296 100644 --- a/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java +++ b/src/main/java/com/gmail/nossr50/runnables/skills/RuptureTask.java @@ -1,6 +1,5 @@ package com.gmail.nossr50.runnables.skills; -import com.gmail.nossr50.datatypes.MobHealthbarType; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.events.skills.rupture.McMMOEntityDamageByRuptureEvent; import com.gmail.nossr50.mcMMO; diff --git a/src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java b/src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java index a0758bacc..08fd39ab4 100644 --- a/src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java +++ b/src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java @@ -14,10 +14,9 @@ import com.gmail.nossr50.util.MetadataConstants; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.ParticleEffectUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.skills.SkillUtils; import org.bukkit.Location; import org.bukkit.entity.Entity; @@ -94,7 +93,8 @@ public class AcrobaticsManager extends SkillManager { double modifiedDamage = Acrobatics.calculateModifiedDodgeDamage(damage, Acrobatics.dodgeDamageModifier); Player player = getPlayer(); - if (!isFatal(modifiedDamage) && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ACROBATICS_DODGE, player)) { + if (!isFatal(modifiedDamage) + && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ACROBATICS_DODGE, player)) { ParticleEffectUtils.playDodgeEffect(player); if (mmoPlayer.useChatNotifications()) { diff --git a/src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java b/src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java index 56dd26aaa..7cc028e8f 100644 --- a/src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java +++ b/src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java @@ -10,9 +10,8 @@ import com.gmail.nossr50.util.MetadataConstants; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import org.bukkit.Location; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; @@ -53,7 +52,7 @@ public class ArcheryManager extends SkillManager { * @param target The {@link LivingEntity} damaged by the arrow * @param arrow The {@link Entity} who shot the arrow */ - public double distanceXpBonusMultiplier(LivingEntity target, Entity arrow) { + public static double distanceXpBonusMultiplier(LivingEntity target, Entity arrow) { //Hacky Fix - some plugins spawn arrows and assign them to players after the ProjectileLaunchEvent fires if(!arrow.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) return 1; @@ -89,7 +88,7 @@ public class ArcheryManager extends SkillManager { * @param defender The {@link Player} being affected by the ability */ public double daze(Player defender) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ARCHERY_DAZE, getPlayer())) { + if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.ARCHERY_DAZE, getPlayer())) { return 0; } @@ -118,10 +117,10 @@ public class ArcheryManager extends SkillManager { * @param oldDamage The raw damage value of this arrow before we modify it */ public double skillShot(double oldDamage) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.ARCHERY_SKILL_SHOT, getPlayer())) { + if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.ARCHERY_SKILL_SHOT, getPlayer())) { + return Archery.getSkillShotBonusDamage(getPlayer(), oldDamage); + } else { return oldDamage; } - - return Archery.getSkillShotBonusDamage(getPlayer(), oldDamage); } } diff --git a/src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java b/src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java index d7ce592d8..d80f7292f 100644 --- a/src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java +++ b/src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java @@ -11,8 +11,11 @@ import com.gmail.nossr50.skills.SkillManager; 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.RandomChanceUtil; -import com.gmail.nossr50.util.skills.*; +import com.gmail.nossr50.util.random.ProbabilityUtil; +import com.gmail.nossr50.util.skills.CombatUtils; +import com.gmail.nossr50.util.skills.ParticleEffectUtils; +import com.gmail.nossr50.util.skills.RankUtils; +import com.gmail.nossr50.util.skills.SkillUtils; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -66,11 +69,11 @@ public class AxesManager extends SkillManager { * Handle the effects of the Axe Mastery ability */ public double axeMastery() { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.AXES_AXE_MASTERY, getPlayer())) { - return 0; + if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.AXES_AXE_MASTERY, getPlayer())) { + return Axes.getAxeMasteryBonusDamage(getPlayer()); } - return Axes.getAxeMasteryBonusDamage(getPlayer()); + return 0; } /** @@ -80,7 +83,7 @@ public class AxesManager extends SkillManager { * @param damage The amount of damage initially dealt by the event */ public double criticalHit(LivingEntity target, double damage) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.AXES_CRITICAL_STRIKES, getPlayer())) { + if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_CRITICAL_STRIKES, getPlayer())) { return 0; } @@ -115,7 +118,7 @@ public class AxesManager extends SkillManager { for (ItemStack armor : target.getEquipment().getArmorContents()) { if (armor != null && ItemUtils.isArmor(armor)) { - if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_ARMOR_IMPACT, getPlayer())) { + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_ARMOR_IMPACT, getPlayer())) { SkillUtils.handleArmorDurabilityChange(armor, durabilityDamage, 1); } } @@ -133,7 +136,7 @@ public class AxesManager extends SkillManager { */ public double greaterImpact(@NotNull LivingEntity target) { //static chance (3rd param) - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_GREATER_IMPACT, getPlayer())) { + if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.AXES_GREATER_IMPACT, getPlayer())) { return 0; } diff --git a/src/main/java/com/gmail/nossr50/skills/child/ChildConfig.java b/src/main/java/com/gmail/nossr50/skills/child/ChildConfig.java deleted file mode 100644 index 0e200bafa..000000000 --- a/src/main/java/com/gmail/nossr50/skills/child/ChildConfig.java +++ /dev/null @@ -1,64 +0,0 @@ -package com.gmail.nossr50.skills.child; - -import com.gmail.nossr50.config.BukkitConfig; -import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.LogUtils; -import com.gmail.nossr50.util.text.StringUtils; -import org.bukkit.configuration.file.YamlConfiguration; - -import java.util.EnumSet; -import java.util.Locale; - -public class ChildConfig extends BukkitConfig { - public ChildConfig() { - super("child.yml"); - loadKeys(); - } - - @Override - protected void loadKeys() { - config.setDefaults(YamlConfiguration.loadConfiguration(mcMMO.p.getResourceAsReader("child.yml"))); - - FamilyTree.clearRegistrations(); // when reloading, need to clear statics - - for (PrimarySkillType skill : mcMMO.p.getSkillTools().CHILD_SKILLS) { - LogUtils.debug(mcMMO.p.getLogger(), "Finding parents of " + skill.name()); - - EnumSet parentSkills = EnumSet.noneOf(PrimarySkillType.class); - boolean useDefaults = false; // If we had an error we back out and use defaults - - for (String name : config.getStringList(StringUtils.getCapitalized(skill.name()))) { - try { - PrimarySkillType parentSkill = PrimarySkillType.valueOf(name.toUpperCase(Locale.ENGLISH)); - FamilyTree.enforceNotChildSkill(parentSkill); - parentSkills.add(parentSkill); - } - catch (IllegalArgumentException ex) { - mcMMO.p.getLogger().warning(name + " is not a valid skill type, or is a child skill!"); - useDefaults = true; - break; - } - } - - if (useDefaults) { - parentSkills.clear(); - for (String name : config.getDefaults().getStringList(StringUtils.getCapitalized(skill.name()))) { - /* We do less checks in here because it's from inside our jar. - * If they're dedicated enough to have modified it, they can have the errors it may produce. - * Alternatively, this can be used to allow child skills to be parent skills, provided there are no circular dependencies this is an advanced sort of configuration. - */ - parentSkills.add(PrimarySkillType.valueOf(name.toUpperCase(Locale.ENGLISH))); - } - } - - // Register them - for (PrimarySkillType parentSkill : parentSkills) { - LogUtils.debug(mcMMO.p.getLogger(), "Registering " + parentSkill.name() + " as parent of " + skill.name()); - FamilyTree.registerParent(skill, parentSkill); - } - } - - FamilyTree.closeRegistration(); - } -} diff --git a/src/main/java/com/gmail/nossr50/skills/child/FamilyTree.java b/src/main/java/com/gmail/nossr50/skills/child/FamilyTree.java deleted file mode 100644 index 0be533600..000000000 --- a/src/main/java/com/gmail/nossr50/skills/child/FamilyTree.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.gmail.nossr50.skills.child; - -import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -import com.gmail.nossr50.util.skills.SkillTools; - -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.Set; - -public class FamilyTree { - private static final HashMap> tree = new HashMap<>(); - - public static Set getParents(PrimarySkillType childSkill) { - enforceChildSkill(childSkill); - - // We do not check if we have the child skill in question, as not having it would mean we did something wrong, and an NPE is desired. - return tree.get(childSkill); - } - - protected static void registerParent(PrimarySkillType childSkill, PrimarySkillType parentSkill) { - enforceChildSkill(childSkill); - enforceNotChildSkill(parentSkill); - - if (!tree.containsKey(childSkill)) { - tree.put(childSkill, EnumSet.noneOf(PrimarySkillType.class)); - } - - tree.get(childSkill).add(parentSkill); - } - - protected static void closeRegistration() { - for (PrimarySkillType childSkill : tree.keySet()) { - Set immutableSet = Collections.unmodifiableSet(tree.get(childSkill)); - tree.put(childSkill, immutableSet); - } - } - - protected static void clearRegistrations() { - tree.clear(); - } - - protected static void enforceChildSkill(PrimarySkillType skill) { - if (!SkillTools.isChildSkill(skill)) { - throw new IllegalArgumentException(skill.name() + " is not a child skill!"); - } - } - - protected static void enforceNotChildSkill(PrimarySkillType skill) { - if (SkillTools.isChildSkill(skill)) { - throw new IllegalArgumentException(skill.name() + " is a child skill!"); - } - } -} diff --git a/src/main/java/com/gmail/nossr50/skills/crossbows/Crossbows.java b/src/main/java/com/gmail/nossr50/skills/crossbows/Crossbows.java new file mode 100644 index 000000000..a64b023af --- /dev/null +++ b/src/main/java/com/gmail/nossr50/skills/crossbows/Crossbows.java @@ -0,0 +1,41 @@ +package com.gmail.nossr50.skills.crossbows; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.util.player.UserManager; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.plugin.Plugin; + +import static com.gmail.nossr50.util.skills.ProjectileUtils.getNormal; + +/** + * Util class for crossbows. + */ +public class Crossbows { + /** + * Process events that may happen from a crossbow hitting an entity. + * + * @param event the projectile hit event + * @param pluginRef the plugin ref + * @param arrow the arrow + */ + public static void processCrossbows(ProjectileHitEvent event, Plugin pluginRef, Arrow arrow) { + if (arrow.getShooter() instanceof Player) { + McMMOPlayer mmoPlayer = UserManager.getPlayer((Player) arrow.getShooter()); + if (mmoPlayer == null) + return; + + processTrickShot(event, pluginRef, arrow, mmoPlayer); + } + } + + private static void processTrickShot(ProjectileHitEvent event, Plugin pluginRef, Arrow arrow, McMMOPlayer mmoPlayer) { + if(event.getHitBlock() != null && event.getHitBlockFace() != null) { + mmoPlayer.getCrossbowsManager().handleRicochet( + pluginRef, + arrow, + getNormal(event.getHitBlockFace())); + } + } +} diff --git a/src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowsManager.java b/src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowsManager.java new file mode 100644 index 000000000..60def74bf --- /dev/null +++ b/src/main/java/com/gmail/nossr50/skills/crossbows/CrossbowsManager.java @@ -0,0 +1,108 @@ +package com.gmail.nossr50.skills.crossbows; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.skills.SkillManager; +import com.gmail.nossr50.util.MetadataConstants; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.random.ProbabilityUtil; +import com.gmail.nossr50.util.skills.ProjectileUtils; +import com.gmail.nossr50.util.skills.RankUtils; +import org.bukkit.Location; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.Player; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.Plugin; +import org.bukkit.projectiles.ProjectileSource; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +import static com.gmail.nossr50.util.skills.CombatUtils.delayArrowMetaCleanup; + +public class CrossbowsManager extends SkillManager { + public CrossbowsManager(McMMOPlayer mmoPlayer) { + super(mmoPlayer, PrimarySkillType.CROSSBOWS); + } + + public void handleRicochet(@NotNull Plugin pluginRef, @NotNull Arrow arrow, @NotNull Vector hitBlockNormal) { + if(!arrow.isShotFromCrossbow()) + return; + + // Check player permission + if (!Permissions.trickShot(mmoPlayer.getPlayer())) { + return; + } + + // TODO: Add an event for this for plugins to hook into + spawnReflectedArrow(pluginRef, arrow, arrow.getLocation(), hitBlockNormal); + } + + private void spawnReflectedArrow(@NotNull Plugin pluginRef, @NotNull Arrow originalArrow, + @NotNull Location origin, @NotNull Vector normal) { + int bounceCount = 0; + + if (originalArrow.hasMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT)) { + bounceCount = originalArrow.getMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT).get(0).asInt(); + if (bounceCount >= getTrickShotMaxBounceCount()) { + return; + } + } + + final ProjectileSource originalArrowShooter = originalArrow.getShooter(); + final Vector arrowInBlockVector = originalArrow.getVelocity(); + final Vector reflectedDirection = arrowInBlockVector.subtract(normal.multiply(2 * arrowInBlockVector.dot(normal))); + final Vector inverseNormal = normal.multiply(-1); + + // check the angle of the arrow against the inverse normal to see if the angle was too shallow + // only checks angle on the first bounce + if (bounceCount == 0 && arrowInBlockVector.angle(inverseNormal) < Math.PI / 4) { + return; + } + + // Spawn new arrow with the reflected direction + Arrow spawnedArrow = originalArrow.getWorld().spawnArrow(origin, reflectedDirection, 1, 1); + ProjectileUtils.copyArrowMetadata(pluginRef, originalArrow, spawnedArrow); + originalArrow.remove(); + // copy metadata from old arrow + spawnedArrow.setShooter(originalArrowShooter); + spawnedArrow.setMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT, + new FixedMetadataValue(pluginRef, bounceCount + 1)); + spawnedArrow.setMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW, + new FixedMetadataValue(pluginRef, originalArrowShooter)); + + // Don't allow multi-shot or infinite arrows to be picked up + if (spawnedArrow.hasMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW) + || spawnedArrow.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) { + spawnedArrow.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED); + } + + // Schedule cleanup of metadata in case metadata cleanup fails + delayArrowMetaCleanup(spawnedArrow); + } + + public int getTrickShotMaxBounceCount() { + return RankUtils.getRank(mmoPlayer, SubSkillType.CROSSBOWS_TRICK_SHOT); + } + + public double getPoweredShotBonusDamage(Player player, double oldDamage) + { + double damageBonusPercent = getDamageBonusPercent(player); + double newDamage = oldDamage + (oldDamage * damageBonusPercent); + return Math.min(newDamage, (oldDamage + mcMMO.p.getAdvancedConfig().getPoweredShotDamageMax())); + } + + public double getDamageBonusPercent(Player player) { + return ((RankUtils.getRank(player, SubSkillType.CROSSBOWS_POWERED_SHOT)) * (mcMMO.p.getAdvancedConfig().getPoweredShotRankDamageMultiplier()) / 100.0D); + } + + public double poweredShot(double oldDamage) { + if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.CROSSBOWS_POWERED_SHOT, getPlayer())) { + return getPoweredShotBonusDamage(getPlayer(), oldDamage); + } else { + return oldDamage; + } + } +} diff --git a/src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java b/src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java index 876634b46..e3cf20fd3 100644 --- a/src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java +++ b/src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java @@ -2,6 +2,7 @@ package com.gmail.nossr50.skills.excavation; import com.gmail.nossr50.api.ItemSpawnReason; import com.gmail.nossr50.datatypes.experience.XPGainReason; +import com.gmail.nossr50.datatypes.experience.XPGainSource; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; @@ -10,15 +11,19 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.skills.SkillUtils; import org.bukkit.Location; import org.bukkit.block.BlockState; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.VisibleForTesting; import java.util.List; +import static java.util.Objects.requireNonNull; + public class ExcavationManager extends SkillManager { public ExcavationManager(McMMOPlayer mcMMOPlayer) { super(mcMMOPlayer, PrimarySkillType.EXCAVATION); @@ -31,9 +36,9 @@ public class ExcavationManager extends SkillManager { */ public void excavationBlockCheck(BlockState blockState) { int xp = Excavation.getBlockXP(blockState); - + requireNonNull(blockState, "excavationBlockCheck: blockState cannot be null"); if (Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.EXCAVATION_ARCHAEOLOGY)) { - List treasures = Excavation.getTreasures(blockState); + List treasures = getTreasures(blockState); if (!treasures.isEmpty()) { int skillLevel = getSkillLevel(); @@ -41,21 +46,35 @@ public class ExcavationManager extends SkillManager { for (ExcavationTreasure treasure : treasures) { if (skillLevel >= treasure.getDropLevel() - && RandomChanceUtil.checkRandomChanceExecutionSuccess(getPlayer(), PrimarySkillType.EXCAVATION, treasure.getDropChance())) { - - //Spawn Vanilla XP orbs if a dice roll succeeds - if(RandomChanceUtil.rollDice(getArchaelogyExperienceOrbChance(), 100)) { - Misc.spawnExperienceOrb(location, getExperienceOrbsReward()); - } - - xp += treasure.getXp(); - Misc.spawnItem(getPlayer(), location, treasure.getDrop(), ItemSpawnReason.EXCAVATION_TREASURE); + && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.EXCAVATION, getPlayer(), treasure.getDropProbability())) { + processExcavationBonusesOnBlock(blockState, treasure, location); } } } } - applyXpGain(xp, XPGainReason.PVE); + applyXpGain(xp, XPGainReason.PVE, XPGainSource.SELF); + } + + @VisibleForTesting + public List getTreasures(@NotNull BlockState blockState) { + requireNonNull(blockState, "blockState cannot be null"); + return Excavation.getTreasures(blockState); + } + + @VisibleForTesting + public void processExcavationBonusesOnBlock(BlockState blockState, ExcavationTreasure treasure, Location location) { + //Spawn Vanilla XP orbs if a dice roll succeeds + if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.EXCAVATION, getPlayer(), getArchaelogyExperienceOrbChance())) { + Misc.spawnExperienceOrb(location, getExperienceOrbsReward()); + } + + int xp = 0; + xp += treasure.getXp(); + Misc.spawnItem(getPlayer(), location, treasure.getDrop(), ItemSpawnReason.EXCAVATION_TREASURE); + if (xp > 0) { + applyXpGain(xp, XPGainReason.PVE, XPGainSource.SELF); + } } public int getExperienceOrbsReward() { diff --git a/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java b/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java index 7f3767986..6063add73 100644 --- a/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java @@ -18,8 +18,7 @@ import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.compat.layers.skills.MasterAnglerCompatibilityLayer; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceSkillStatic; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.skills.SkillUtils; @@ -61,11 +60,14 @@ public class FishingManager extends SkillManager { } public boolean canShake(Entity target) { - return target instanceof LivingEntity && RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.FISHING_SHAKE) && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_SHAKE); + return target instanceof LivingEntity && RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.FISHING_SHAKE) + && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_SHAKE); } public boolean canMasterAngler() { - return mcMMO.getCompatibilityManager().getMasterAnglerCompatibilityLayer() != null && getSkillLevel() >= RankUtils.getUnlockLevel(SubSkillType.FISHING_MASTER_ANGLER) && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_MASTER_ANGLER); + return mcMMO.getCompatibilityManager().getMasterAnglerCompatibilityLayer() != null + && getSkillLevel() >= RankUtils.getUnlockLevel(SubSkillType.FISHING_MASTER_ANGLER) + && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_MASTER_ANGLER); } // public void setFishingRodCastTimestamp() @@ -477,8 +479,8 @@ public class FishingManager extends SkillManager { * * @param target The {@link LivingEntity} affected by the ability */ - public void shakeCheck(LivingEntity target) { - if (RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getShakeChance(), getPlayer(), SubSkillType.FISHING_SHAKE))) { + public void shakeCheck(@NotNull LivingEntity target) { + if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.FISHING, getPlayer(), getShakeChance())) { List possibleDrops = Fishing.findPossibleDrops(target); if (possibleDrops == null || possibleDrops.isEmpty()) { diff --git a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java index d97b12db8..a2eb57ae1 100644 --- a/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java +++ b/src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java @@ -20,10 +20,8 @@ import com.gmail.nossr50.runnables.skills.DelayedHerbalismXPCheckTask; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceSkillStatic; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.skills.SkillUtils; import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; @@ -650,7 +648,7 @@ public class HerbalismManager extends SkillManager { * @return true if the ability was successful, false otherwise */ public boolean processGreenThumbBlocks(BlockState blockState) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_GREEN_THUMB, getPlayer())) { + if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_GREEN_THUMB, getPlayer())) { NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE_FAILED, "Herbalism.Ability.GTh.Fail"); return false; } @@ -665,7 +663,7 @@ public class HerbalismManager extends SkillManager { * @return true if the ability was successful, false otherwise */ public boolean processHylianLuck(BlockState blockState) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_HYLIAN_LUCK, getPlayer())) { + if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_HYLIAN_LUCK, getPlayer())) { return false; } @@ -684,7 +682,7 @@ public class HerbalismManager extends SkillManager { for (HylianTreasure treasure : treasures) { if (skillLevel >= treasure.getDropLevel() - && RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(treasure.getDropChance(), getPlayer(), SubSkillType.HERBALISM_HYLIAN_LUCK))) { + && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.HERBALISM, player, treasure.getDropChance())) { if (!EventUtils.simulateBlockBreak(blockState.getBlock(), player)) { return false; } @@ -721,7 +719,7 @@ public class HerbalismManager extends SkillManager { playerInventory.removeItem(new ItemStack(Material.RED_MUSHROOM)); player.updateInventory(); - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_SHROOM_THUMB, player)) { + if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_SHROOM_THUMB, player)) { NotificationManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE_FAILED, "Herbalism.Ability.ShroomThumb.Fail"); return false; } @@ -805,7 +803,7 @@ public class HerbalismManager extends SkillManager { return false; } - if (!greenTerra && !RandomChanceUtil.checkRandomChanceExecutionSuccess(player, SubSkillType.HERBALISM_GREEN_THUMB, true)) { + if (!greenTerra && !ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.HERBALISM_GREEN_THUMB, player)) { return false; } diff --git a/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java b/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java index 030104d4f..b91274ce4 100644 --- a/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java +++ b/src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java @@ -13,7 +13,7 @@ import com.gmail.nossr50.runnables.skills.AbilityCooldownTask; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.skills.SkillUtils; import org.apache.commons.lang.math.RandomUtils; @@ -35,7 +35,7 @@ public class MiningManager extends SkillManager { public static final String BUDDING_AMETHYST = "budding_amethyst"; - public MiningManager(McMMOPlayer mcMMOPlayer) { + public MiningManager(@NotNull McMMOPlayer mcMMOPlayer) { super(mcMMOPlayer, PrimarySkillType.MINING); } @@ -70,6 +70,11 @@ public class MiningManager extends SkillManager { return RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.MINING_DOUBLE_DROPS) && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.MINING_DOUBLE_DROPS); } + public boolean canMotherLode() { + return Permissions.canUseSubSkill(getPlayer(), SubSkillType.MINING_MOTHER_LODE); + } + + /** * Process double drops & XP gain for Mining. * @@ -96,9 +101,32 @@ public class MiningManager extends SkillManager { if(silkTouch && !mcMMO.p.getAdvancedConfig().getDoubleDropSilkTouchEnabled()) return; + //Mining mastery allows for a chance of triple drops + if(canMotherLode()) { + //Triple Drops failed so do a normal double drops check + if(!processTripleDrops(blockState)) { + processDoubleDrops(blockState); + } + } else { + //If the user has no mastery, proceed with normal double drop routine + processDoubleDrops(blockState); + } + } + + private boolean processTripleDrops(@NotNull BlockState blockState) { //TODO: Make this readable - if (RandomChanceUtil.checkRandomChanceExecutionSuccess(getPlayer(), SubSkillType.MINING_DOUBLE_DROPS, true)) { - boolean useTriple = mmoPlayer.getAbilityMode(mcMMO.p.getSkillTools().getSuperAbility(skill)) && mcMMO.p.getAdvancedConfig().getAllowMiningTripleDrops(); + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.MINING_MOTHER_LODE, getPlayer())) { + BlockUtils.markDropsAsBonus(blockState, 2); + return true; + } else { + return false; + } + } + + private void processDoubleDrops(@NotNull BlockState blockState) { + //TODO: Make this readable + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.MINING_DOUBLE_DROPS, getPlayer())) { + boolean useTriple = mmoPlayer.getAbilityMode(SuperAbilityType.SUPER_BREAKER) && mcMMO.p.getAdvancedConfig().getAllowMiningTripleDrops(); BlockUtils.markDropsAsBonus(blockState, useTriple); } } diff --git a/src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java b/src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java index 284f875c6..fb9d6ceb3 100644 --- a/src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java +++ b/src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java @@ -14,10 +14,8 @@ import com.gmail.nossr50.util.EventUtils; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceSkillStatic; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.skills.SkillUtils; import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; @@ -322,7 +320,7 @@ public class RepairManager extends SkillManager { if(!RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.REPAIR_SUPER_REPAIR)) return false; - if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.REPAIR_SUPER_REPAIR, getPlayer())) { + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.REPAIR_SUPER_REPAIR, getPlayer())) { NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Repair.Skills.FeltEasy"); return true; } @@ -373,10 +371,10 @@ public class RepairManager extends SkillManager { Enchantment enchantment = enchant.getKey(); - if (RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getKeepEnchantChance(), getPlayer(), SubSkillType.REPAIR_ARCANE_FORGING))) { + if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.REPAIR, getPlayer(), getKeepEnchantChance())) { if (ArcaneForging.arcaneForgingDowngrades && enchantLevel > 1 - && (!RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(100 - getDowngradeEnchantChance(), getPlayer(), SubSkillType.REPAIR_ARCANE_FORGING)))) { + && (!ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.REPAIR, getPlayer(), 100 - getDowngradeEnchantChance()))) { item.addUnsafeEnchantment(enchantment, enchantLevel - 1); downgraded = true; } diff --git a/src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java b/src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java index 4e4c04ff3..7546b1c99 100644 --- a/src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java +++ b/src/main/java/com/gmail/nossr50/skills/salvage/SalvageManager.java @@ -14,8 +14,7 @@ import com.gmail.nossr50.util.EventUtils; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceSkillStatic; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; import com.gmail.nossr50.util.skills.SkillUtils; import com.gmail.nossr50.util.sounds.SoundManager; @@ -121,7 +120,7 @@ public class SalvageManager extends SkillManager { for(int x = 0; x < potentialSalvageYield-1; x++) { - if(RandomChanceUtil.rollDice(chanceOfSuccess, 100)) { + if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SALVAGE, player, chanceOfSuccess)) { chanceOfSuccess-=3; chanceOfSuccess = Math.max(chanceOfSuccess, 90); @@ -252,12 +251,12 @@ public class SalvageManager extends SkillManager { if (!Salvage.arcaneSalvageEnchantLoss || Permissions.hasSalvageEnchantBypassPerk(player) - || RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getExtractFullEnchantChance(), getPlayer(), SubSkillType.SALVAGE_ARCANE_SALVAGE))) { + || ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SALVAGE, player, getExtractFullEnchantChance())) { enchantMeta.addStoredEnchant(enchant.getKey(), enchantLevel, true); } else if (enchantLevel > 1 && Salvage.arcaneSalvageDowngrades - && RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getExtractPartialEnchantChance(), getPlayer(), SubSkillType.SALVAGE_ARCANE_SALVAGE))) { + && ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SALVAGE, player, getExtractPartialEnchantChance())) { enchantMeta.addStoredEnchant(enchant.getKey(), enchantLevel - 1, true); downgraded = true; } else { diff --git a/src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java b/src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java index 98d5a5a6f..9bb4a6e39 100644 --- a/src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java @@ -8,9 +8,8 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.Permissions; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import org.bukkit.block.Furnace; import org.bukkit.event.inventory.FurnaceBurnEvent; import org.bukkit.event.inventory.FurnaceSmeltEvent; @@ -25,7 +24,7 @@ public class SmeltingManager extends SkillManager { public boolean isSecondSmeltSuccessful() { return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.SMELTING_SECOND_SMELT) - && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SMELTING_SECOND_SMELT, getPlayer()); + && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.SMELTING_SECOND_SMELT, getPlayer()); } /** 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 a95f1b9a4..d68d98e8d 100644 --- a/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java +++ b/src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java @@ -14,10 +14,9 @@ import com.gmail.nossr50.util.ItemUtils; 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.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -76,7 +75,8 @@ public class SwordsManager extends SkillManager { return; //Don't apply bleed } - if (RandomChanceUtil.rollDice(mcMMO.p.getAdvancedConfig().getRuptureChanceToApplyOnHit(getRuptureRank()), 100)) { + double ruptureOdds = mcMMO.p.getAdvancedConfig().getRuptureChanceToApplyOnHit(getRuptureRank()); + if (ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.SWORDS, this.getPlayer(), ruptureOdds)) { if (target instanceof Player defender) { @@ -141,7 +141,8 @@ public class SwordsManager extends SkillManager { * @param damage The amount of damage initially dealt by the event */ public void counterAttackChecks(@NotNull LivingEntity attacker, double damage) { - if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SWORDS_COUNTER_ATTACK, getPlayer())) { + + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.SWORDS_COUNTER_ATTACK, getPlayer())) { CombatUtils.dealDamage(attacker, damage / Swords.counterAttackModifier, getPlayer()); NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Swords.Combat.Countered"); diff --git a/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java b/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java index 1359a041b..dcee2e0bd 100644 --- a/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java @@ -15,11 +15,9 @@ import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceSkillStatic; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.ParticleEffectUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.sounds.SoundManager; import com.gmail.nossr50.util.sounds.SoundType; import com.gmail.nossr50.util.text.StringUtils; @@ -145,7 +143,7 @@ public class TamingManager extends SkillManager { * @param damage The damage being absorbed by the wolf */ public void fastFoodService(@NotNull Wolf wolf, double damage) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.TAMING_FAST_FOOD_SERVICE, getPlayer())) { + if (!ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.TAMING_FAST_FOOD_SERVICE, getPlayer())) { return; } @@ -165,12 +163,6 @@ public class TamingManager extends SkillManager { * @param damage The initial damage */ public double gore(@NotNull LivingEntity target, double damage) { -// if (target instanceof Player) { -// NotificationManager.sendPlayerInformation((Player)target, NotificationType.SUBSKILL_MESSAGE, "Combat.StruckByGore"); -// } -// -// NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Combat.Gore"); - damage = (damage * Taming.goreModifier) - damage; return damage; @@ -270,7 +262,7 @@ public class TamingManager extends SkillManager { if(!RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.TAMING_PUMMEL)) return; - if(!RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(mcMMO.p.getAdvancedConfig().getPummelChance(), getPlayer(), SubSkillType.TAMING_PUMMEL))) + if(!ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.TAMING, getPlayer(), mcMMO.p.getAdvancedConfig().getPummelChance())) return; ParticleEffectUtils.playGreaterImpactEffect(target); @@ -380,17 +372,12 @@ public class TamingManager extends SkillManager { } private void spawnCOTWEntity(CallOfTheWildType callOfTheWildType, Location spawnLocation, EntityType entityType) { - switch(callOfTheWildType) { - case CAT: + switch (callOfTheWildType) { + case CAT -> //Entity type is needed for cats because in 1.13 and below we spawn ocelots, in 1.14 and above we spawn cats - spawnCat(spawnLocation, entityType); - break; - case HORSE: - spawnHorse(spawnLocation); - break; - case WOLF: - spawnWolf(spawnLocation); - break; + spawnCat(spawnLocation, entityType); + case HORSE -> spawnHorse(spawnLocation); + case WOLF -> spawnWolf(spawnLocation); } } diff --git a/src/main/java/com/gmail/nossr50/skills/tridents/TridentsManager.java b/src/main/java/com/gmail/nossr50/skills/tridents/TridentsManager.java new file mode 100644 index 000000000..bd4296d0f --- /dev/null +++ b/src/main/java/com/gmail/nossr50/skills/tridents/TridentsManager.java @@ -0,0 +1,35 @@ +package com.gmail.nossr50.skills.tridents; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.datatypes.skills.ToolType; +import com.gmail.nossr50.skills.SkillManager; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.skills.RankUtils; + +public class TridentsManager extends SkillManager { + public TridentsManager(McMMOPlayer mmoPlayer) { + super(mmoPlayer, PrimarySkillType.TRIDENTS); + } + + /** + * Checks if the player can activate the Super Ability for Tridents + * @return true if the player can activate the Super Ability, false otherwise + */ + public boolean canActivateAbility() { + return mmoPlayer.getToolPreparationMode(ToolType.TRIDENTS) && Permissions.tridentsSuper(getPlayer()); + } + + public double impaleDamageBonus() { + int rank = RankUtils.getRank(getPlayer(), SubSkillType.TRIDENTS_IMPALE); + + if(rank > 1) { + return (1.0D + (rank * .5D)); + } else if(rank == 1) { + return 1.0D; + } + + return 0.0D; + } +} diff --git a/src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java b/src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java index 93dc5eef5..dbc8060ef 100644 --- a/src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java +++ b/src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java @@ -12,9 +12,8 @@ import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import org.bukkit.Material; import org.bukkit.block.BlockState; import org.bukkit.entity.Item; @@ -68,7 +67,7 @@ public class UnarmedManager extends SkillManager { } public boolean blockCrackerCheck(@NotNull BlockState blockState) { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.UNARMED_BLOCK_CRACKER, getPlayer())) { + if (!ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.UNARMED_BLOCK_CRACKER, getPlayer())) { return false; } @@ -99,7 +98,7 @@ public class UnarmedManager extends SkillManager { * @param defender The defending player */ public void disarmCheck(@NotNull Player defender) { - if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.UNARMED_DISARM, getPlayer()) && !hasIronGrip(defender)) { + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_DISARM, getPlayer()) && !hasIronGrip(defender)) { if (EventUtils.callDisarmEvent(defender).isCancelled()) { return; } @@ -122,7 +121,7 @@ public class UnarmedManager extends SkillManager { * Check for arrow deflection. */ public boolean deflectCheck() { - if (RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.UNARMED_ARROW_DEFLECT, getPlayer())) { + if (ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_ARROW_DEFLECT, getPlayer())) { NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Combat.ArrowDeflect"); return true; } @@ -145,11 +144,11 @@ public class UnarmedManager extends SkillManager { * Handle the effects of the Iron Arm ability */ public double calculateSteelArmStyleDamage() { - if (!RandomChanceUtil.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.UNARMED_STEEL_ARM_STYLE, getPlayer())) { - return 0; + if (ProbabilityUtil.isNonRNGSkillActivationSuccessful(SubSkillType.UNARMED_STEEL_ARM_STYLE, getPlayer())) { + return getSteelArmStyleDamage(); } - return getSteelArmStyleDamage(); + return 0; } public double getSteelArmStyleDamage() { @@ -179,7 +178,7 @@ public class UnarmedManager extends SkillManager { private boolean hasIronGrip(@NotNull Player defender) { if (!Misc.isNPCEntityExcludingVillagers(defender) && Permissions.isSubSkillEnabled(defender, SubSkillType.UNARMED_IRON_GRIP) - && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.UNARMED_IRON_GRIP, defender)) { + && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.UNARMED_IRON_GRIP, defender)) { NotificationManager.sendPlayerInformation(defender, NotificationType.SUBSKILL_MESSAGE, "Unarmed.Ability.IronGrip.Defender"); NotificationManager.sendPlayerInformation(getPlayer(), NotificationType.SUBSKILL_MESSAGE, "Unarmed.Ability.IronGrip.Attacker"); diff --git a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java index c59a7e7ac..dd198131e 100644 --- a/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java +++ b/src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java @@ -14,10 +14,9 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.skills.SkillManager; import com.gmail.nossr50.util.*; import com.gmail.nossr50.util.player.NotificationManager; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import com.gmail.nossr50.util.skills.CombatUtils; import com.gmail.nossr50.util.skills.RankUtils; -import com.gmail.nossr50.util.skills.SkillActivationType; import com.gmail.nossr50.util.skills.SkillUtils; import org.bukkit.Bukkit; import org.bukkit.Material; @@ -35,7 +34,9 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +//TODO: Seems to not be using the item drop event for bonus drops, may want to change that.. or may not be able to be changed? public class WoodcuttingManager extends SkillManager { private boolean treeFellerReachedThreshold = false; private static int treeFellerThreshold; //TODO: Shared setting, will be removed in 2.2 @@ -68,21 +69,46 @@ public class WoodcuttingManager extends SkillManager { && ItemUtils.isAxe(heldItem); } - private boolean checkHarvestLumberActivation(@NotNull Material material) { + private boolean checkHarvestLumberActivation(Material material) { return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER) && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER) - && RandomChanceUtil.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer()) + && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer()) + && mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, material); + } + + private boolean checkCleanCutsActivation(Material material) { + return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER) + && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER) + && ProbabilityUtil.isSkillRNGSuccessful(SubSkillType.WOODCUTTING_CLEAN_CUTS, getPlayer()) && mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, material); } /** - * Begins Woodcutting + * Processes bonus drops for a block * * @param blockState Block being broken */ - public void processHarvestLumber(@NotNull BlockState blockState) { - if (checkHarvestLumberActivation(blockState.getType())) { - spawnHarvestLumberBonusDrops(blockState); + public void processBonusDropCheck(@NotNull BlockState blockState) { + //TODO: Why isn't this using the item drop event? Potentially because of Tree Feller? This should be adjusted either way. + if(mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, blockState.getType())) { + //Mastery enabled for player + if(Permissions.canUseSubSkill(getPlayer(), SubSkillType.WOODCUTTING_CLEAN_CUTS)) { + if(checkCleanCutsActivation(blockState.getType())) { + //Triple drops + spawnHarvestLumberBonusDrops(blockState); + spawnHarvestLumberBonusDrops(blockState); + } else { + //Harvest Lumber Check + if(checkHarvestLumberActivation(blockState.getType())) { + spawnHarvestLumberBonusDrops(blockState); + } + } + //No Mastery (no Clean Cuts) + } else if (Permissions.canUseSubSkill(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)) { + if(checkHarvestLumberActivation(blockState.getType())) { + spawnHarvestLumberBonusDrops(blockState); + } + } } } @@ -295,24 +321,23 @@ public class WoodcuttingManager extends SkillManager { Misc.spawnItemsFromCollection(getPlayer(), Misc.getBlockCenter(blockState), block.getDrops(itemStack), ItemSpawnReason.TREE_FELLER_DISPLACED_BLOCK); //Bonus Drops / Harvest lumber checks - processHarvestLumber(blockState); + processBonusDropCheck(blockState); } else if (BlockUtils.isNonWoodPartOfTree(blockState)) { - //Drop displaced non-woodcutting XP blocks - - if(RankUtils.hasUnlockedSubskill(player, SubSkillType.WOODCUTTING_KNOCK_ON_WOOD)) { + // 90% of the time do not drop leaf blocks + if (ThreadLocalRandom.current().nextInt(100) > 90) { Misc.spawnItemsFromCollection(getPlayer(), Misc.getBlockCenter(blockState), block.getDrops(itemStack), ItemSpawnReason.TREE_FELLER_DISPLACED_BLOCK); + } + //Drop displaced non-woodcutting XP blocks + if(RankUtils.hasUnlockedSubskill(player, SubSkillType.WOODCUTTING_KNOCK_ON_WOOD)) { if(RankUtils.hasReachedRank(2, player, SubSkillType.WOODCUTTING_KNOCK_ON_WOOD)) { if(mcMMO.p.getAdvancedConfig().isKnockOnWoodXPOrbEnabled()) { - if(RandomChanceUtil.rollDice(10, 100)) { + if(ProbabilityUtil.isStaticSkillRNGSuccessful(PrimarySkillType.WOODCUTTING, player, 10)) { int randOrbCount = Math.max(1, Misc.getRandom().nextInt(100)); Misc.spawnExperienceOrb(blockState.getLocation(), randOrbCount); } } } - - } else { - Misc.spawnItemsFromCollection(getPlayer(), Misc.getBlockCenter(blockState), block.getDrops(itemStack), ItemSpawnReason.TREE_FELLER_DISPLACED_BLOCK, 1); } } @@ -381,6 +406,10 @@ public class WoodcuttingManager extends SkillManager { * @param blockState Block being broken */ protected void spawnHarvestLumberBonusDrops(@NotNull BlockState blockState) { - Misc.spawnItemsFromCollection(getPlayer(), Misc.getBlockCenter(blockState), blockState.getBlock().getDrops(getPlayer().getInventory().getItemInMainHand()), ItemSpawnReason.BONUS_DROPS); + Misc.spawnItemsFromCollection( + getPlayer(), + Misc.getBlockCenter(blockState), + blockState.getBlock().getDrops(getPlayer().getInventory().getItemInMainHand()), + ItemSpawnReason.BONUS_DROPS); } } diff --git a/src/main/java/com/gmail/nossr50/util/BlockUtils.java b/src/main/java/com/gmail/nossr50/util/BlockUtils.java index 7ef3cbc81..6edb342a5 100644 --- a/src/main/java/com/gmail/nossr50/util/BlockUtils.java +++ b/src/main/java/com/gmail/nossr50/util/BlockUtils.java @@ -7,8 +7,7 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.skills.repair.Repair; import com.gmail.nossr50.skills.salvage.Salvage; -import com.gmail.nossr50.util.random.RandomChanceSkill; -import com.gmail.nossr50.util.random.RandomChanceUtil; +import com.gmail.nossr50.util.random.ProbabilityUtil; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.block.Block; @@ -98,7 +97,7 @@ public final class BlockUtils { */ public static boolean checkDoubleDrops(Player player, BlockState blockState, PrimarySkillType skillType, SubSkillType subSkillType) { if (mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(skillType, blockState.getType()) && Permissions.isSubSkillEnabled(player, subSkillType)) { - return RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, true)); + return ProbabilityUtil.isSkillRNGSuccessful(subSkillType, player); } return false; @@ -231,22 +230,22 @@ public final class BlockUtils { return mcMMO.getMaterialMapStore().isTreeFellerDestructible(material); } - /** - * Determine if a given block should be affected by Flux Mining - * - * @param blockState The {@link BlockState} of the block to check - * @return true if the block should affected by Flux Mining, false otherwise - */ - public static boolean affectedByFluxMining(BlockState blockState) { - switch (blockState.getType()) { - case IRON_ORE: - case GOLD_ORE: - return true; - - default: - return false; - } - } +// /** +// * Determine if a given block should be affected by Flux Mining +// * +// * @param blockState The {@link BlockState} of the block to check +// * @return true if the block should affected by Flux Mining, false otherwise +// */ +// public static boolean affectedByFluxMining(BlockState blockState) { +// switch (blockState.getType()) { +// case IRON_ORE: +// case GOLD_ORE: +// return true; +// +// default: +// return false; +// } +// } /** * Determine if a given block can activate Herbalism abilities diff --git a/src/main/java/com/gmail/nossr50/util/EventUtils.java b/src/main/java/com/gmail/nossr50/util/EventUtils.java index c28467e65..1bf700c04 100644 --- a/src/main/java/com/gmail/nossr50/util/EventUtils.java +++ b/src/main/java/com/gmail/nossr50/util/EventUtils.java @@ -183,8 +183,7 @@ public final class EventUtils { * @param subSkillType target subskill * @return the event after it has been fired */ - @Deprecated - public static @NotNull SubSkillEvent callSubSkillEvent(Player player, SubSkillType subSkillType) { + public static @NotNull SubSkillEvent callSubSkillEvent(@NotNull Player player, @NotNull SubSkillType subSkillType) { SubSkillEvent event = new SubSkillEvent(player, subSkillType); mcMMO.p.getServer().getPluginManager().callEvent(event); @@ -399,7 +398,7 @@ public final class EventUtils { McMMOPlayer mmoPlayer = UserManager.getPlayer(player); if(mmoPlayer == null) return true; - + McMMOPlayerXpGainEvent event = new McMMOPlayerXpGainEvent(player, skill, xpGained, xpGainReason); mcMMO.p.getServer().getPluginManager().callEvent(event); diff --git a/src/main/java/com/gmail/nossr50/util/ItemUtils.java b/src/main/java/com/gmail/nossr50/util/ItemUtils.java index 4787e0159..04993eb74 100644 --- a/src/main/java/com/gmail/nossr50/util/ItemUtils.java +++ b/src/main/java/com/gmail/nossr50/util/ItemUtils.java @@ -36,14 +36,26 @@ public final class ItemUtils { * @param item Item to check * @return true if the item is a bow, false otherwise */ + // TODO: Unit tests public static boolean isBow(@NotNull ItemStack item) { return mcMMO.getMaterialMapStore().isBow(item.getType().getKey().getKey()); } + // TODO: Unit tests public static boolean isCrossbow(@NotNull ItemStack item) { return mcMMO.getMaterialMapStore().isCrossbow(item.getType().getKey().getKey()); } + // TODO: Unit tests + public static boolean isBowOrCrossbow(@NotNull ItemStack item) { + return isBow(item) || isCrossbow(item); + } + + // TODO: Unit tests + public static boolean isTrident(@NotNull ItemStack item) { + return mcMMO.getMaterialMapStore().isTrident(item.getType().getKey().getKey()); + } + public static boolean hasItemInEitherHand(@NotNull Player player, Material material) { return player.getInventory().getItemInMainHand().getType() == material || player.getInventory().getItemInOffHand().getType() == material; } diff --git a/src/main/java/com/gmail/nossr50/util/MaterialMapStore.java b/src/main/java/com/gmail/nossr50/util/MaterialMapStore.java index 6a54cd608..b0f623602 100644 --- a/src/main/java/com/gmail/nossr50/util/MaterialMapStore.java +++ b/src/main/java/com/gmail/nossr50/util/MaterialMapStore.java @@ -10,9 +10,6 @@ import java.util.Locale; /** * Stores hash tables for item and block names * This allows for better support across multiple versions of Minecraft - * - * This is a temporary class, mcMMO is spaghetti and I'l clean it up later - * */ public class MaterialMapStore { @@ -52,7 +49,6 @@ public class MaterialMapStore { private final @NotNull HashSet bows; private final @NotNull HashSet crossbows; private final @NotNull HashSet tools; - private final @NotNull HashSet enchantables; private final @NotNull HashSet ores; @@ -820,6 +816,14 @@ public class MaterialMapStore { return crossbows.contains(id); } + public boolean isTrident(@NotNull Material material) { + return isTrident(material.getKey().getKey()); + } + + public boolean isTrident(@NotNull String id) { + return tridents.contains(id); + } + public boolean isLeatherArmor(@NotNull Material material) { return isLeatherArmor(material.getKey().getKey()); } diff --git a/src/main/java/com/gmail/nossr50/util/MetadataConstants.java b/src/main/java/com/gmail/nossr50/util/MetadataConstants.java index 090e04d17..39f9479ce 100644 --- a/src/main/java/com/gmail/nossr50/util/MetadataConstants.java +++ b/src/main/java/com/gmail/nossr50/util/MetadataConstants.java @@ -14,6 +14,9 @@ public class MetadataConstants { * Take great care if you ever modify the value of these keys */ public static final @NotNull String METADATA_KEY_REPLANT = "mcMMO: Recently Replanted"; + public static final @NotNull String METADATA_KEY_SPAWNED_ARROW = "mcMMO: Spawned Arrow"; + public static final @NotNull String METADATA_KEY_MULTI_SHOT_ARROW = "mcMMO: Multi-shot Arrow"; + public static final @NotNull String METADATA_KEY_BOUNCE_COUNT = "mcMMO: Arrow Bounce Count"; public static final @NotNull String METADATA_KEY_EXPLOSION_FROM_RUPTURE = "mcMMO: Rupture Explosion"; public static final @NotNull String METADATA_KEY_FISH_HOOK_REF = "mcMMO: Fish Hook Tracker"; public static final @NotNull String METADATA_KEY_DODGE_TRACKER = "mcMMO: Dodge Tracker"; diff --git a/src/main/java/com/gmail/nossr50/util/Misc.java b/src/main/java/com/gmail/nossr50/util/Misc.java index bbd9bb999..2467a5540 100644 --- a/src/main/java/com/gmail/nossr50/util/Misc.java +++ b/src/main/java/com/gmail/nossr50/util/Misc.java @@ -343,4 +343,26 @@ public final class Misc { experienceOrb.setExperience(orbExpValue); } } + +// public static void hackyUnitTest(@NotNull McMMOPlayer normalPlayer) { +// mcMMO.p.getLogger().info("Starting hacky unit test..."); +// int iterations = 1000000; +// double ratioDivisor = 10000; //10000 because we run the test 1,000,000 times +// double expectedFailRate = 100.0D - RandomChanceUtil.getRandomChanceExecutionSuccess(normalPlayer.getPlayer(), SubSkillType.MINING_MOTHER_LODE, true); +// +// double win = 0, loss = 0; +// for(int x = 0; x < iterations; x++) { +// if(RandomChanceUtil.checkRandomChanceExecutionSuccess(normalPlayer.getPlayer(), SubSkillType.MINING_MOTHER_LODE, true)) { +// win++; +// } else { +// loss++; +// } +// } +// +// double lossRatio = (loss / ratioDivisor); +// mcMMO.p.getLogger().info("Expected Fail Rate: "+expectedFailRate); +// mcMMO.p.getLogger().info("Loss Ratio for hacky test: "+lossRatio); +//// Assert.assertEquals(lossRatio, expectedFailRate, 0.01D); +// } + } diff --git a/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java b/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java index 3d956afe7..5da29a656 100644 --- a/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java +++ b/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java @@ -26,7 +26,7 @@ public final class MobHealthbarUtils { EntityDamageEvent lastDamageCause = player.getLastDamageCause(); String replaceString = lastDamageCause instanceof EntityDamageByEntityEvent ? StringUtils.getPrettyEntityTypeString(((EntityDamageByEntityEvent) lastDamageCause).getDamager().getType()) : "a mob"; - return deathMessage.replaceAll("(?:(\u00A7(?:[0-9A-FK-ORa-fk-or]))*(?:[\u2764\u25A0]{1,10})){1,2}", replaceString); + return deathMessage.replaceAll("(?:(§(?:[0-9A-FK-ORa-fk-or]))*(?:[❤■]{1,10})){1,2}", replaceString); } /** diff --git a/src/main/java/com/gmail/nossr50/util/Permissions.java b/src/main/java/com/gmail/nossr50/util/Permissions.java index c405f84ab..6dcd9b800 100644 --- a/src/main/java/com/gmail/nossr50/util/Permissions.java +++ b/src/main/java/com/gmail/nossr50/util/Permissions.java @@ -5,16 +5,18 @@ import com.gmail.nossr50.datatypes.skills.ItemType; import com.gmail.nossr50.datatypes.skills.MaterialType; 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.mcMMO; +import com.gmail.nossr50.util.skills.RankUtils; import org.bukkit.Material; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; import org.bukkit.permissions.Permissible; import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.PluginManager; +import org.jetbrains.annotations.NotNull; import java.util.Locale; @@ -164,10 +166,14 @@ public final class Permissions { * SKILLS */ - public static boolean skillEnabled(Permissible permissible, PrimarySkillType skill) {return permissible.hasPermission("mcmmo.skills." + skill.toString().toLowerCase(Locale.ENGLISH)); } + public static boolean skillEnabled(Permissible permissible, PrimarySkillType skill) { + return permissible.hasPermission("mcmmo.skills." + skill.toString().toLowerCase(Locale.ENGLISH)); + } + public static boolean vanillaXpBoost(Permissible permissible, PrimarySkillType skill) { return permissible.hasPermission("mcmmo.ability." + skill.toString().toLowerCase(Locale.ENGLISH) + ".vanillaxpboost"); } - public static boolean isSubSkillEnabled(Permissible permissible, SubSkillType subSkillType) { return permissible.hasPermission(subSkillType.getPermissionNodeAddress()); } - public static boolean isSubSkillEnabled(Permissible permissible, AbstractSubSkill abstractSubSkill) { return permissible.hasPermission(abstractSubSkill.getPermissionNode()); } + public static boolean isSubSkillEnabled(Permissible permissible, SubSkillType subSkillType) { + return permissible.hasPermission(subSkillType.getPermissionNodeAddress()); + } /* ACROBATICS */ public static boolean dodge(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.acrobatics.dodge"); } @@ -179,6 +185,7 @@ public final class Permissions { public static boolean concoctions(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.alchemy.concoctions"); } /* ARCHERY */ + public static boolean explosiveShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.explosiveshot"); } public static boolean arrowRetrieval(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.trackarrows"); } public static boolean daze(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.daze"); } @@ -225,6 +232,20 @@ public final class Permissions { /* WOODCUTTING */ public static boolean treeFeller(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.woodcutting.treefeller"); } + /* CROSSBOWS */ + public static boolean superShotgun(Permissible permissible) { + return permissible.hasPermission("mcmmo.ability.crossbows.supershotgun"); + } + public static boolean trickShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.crossbows.trickshot"); } + public static boolean poweredShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.crossbows.poweredshot"); } + + /* TRIDENTS */ + public static boolean tridentsSuper(Permissible permissible) { + return false; + // return permissible.hasPermission("mcmmo.ability.tridents.superability"); + } + public static boolean tridentsLimitBreak(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.tridents.superability"); } + /* * PARTY */ @@ -256,4 +277,15 @@ public final class Permissions { permission.setDefault(permissionDefault); pluginManager.addPermission(permission); } + + /** + * Checks if a player can use a skill + * + * @param player target player + * @param subSkillType target subskill + * @return true if the player has permission and has the skill unlocked + */ + public static boolean canUseSubSkill(@NotNull Player player, @NotNull SubSkillType subSkillType) { + return isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType); + } } diff --git a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java index 371124b06..d272eeb76 100644 --- a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java +++ b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java @@ -61,6 +61,9 @@ public final class CommandRegistrationManager { case AXES: command.setExecutor(new AxesCommand()); break; + case CROSSBOWS: + command.setExecutor(new CrossbowsCommand()); + break; case EXCAVATION: command.setExecutor(new ExcavationCommand()); @@ -97,6 +100,9 @@ public final class CommandRegistrationManager { case TAMING: command.setExecutor(new TamingCommand()); break; + case TRIDENTS: + command.setExecutor(new TridentsCommand()); + break; case UNARMED: command.setExecutor(new UnarmedCommand()); diff --git a/src/main/java/com/gmail/nossr50/util/random/InvalidActivationException.java b/src/main/java/com/gmail/nossr50/util/random/InvalidActivationException.java deleted file mode 100644 index 677d3e63e..000000000 --- a/src/main/java/com/gmail/nossr50/util/random/InvalidActivationException.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.gmail.nossr50.util.random; - -public class InvalidActivationException extends Exception { - //Weee -} diff --git a/src/main/java/com/gmail/nossr50/util/random/Probability.java b/src/main/java/com/gmail/nossr50/util/random/Probability.java new file mode 100644 index 000000000..4bb9dacc9 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/random/Probability.java @@ -0,0 +1,65 @@ +package com.gmail.nossr50.util.random; + +import org.jetbrains.annotations.NotNull; + +import java.util.concurrent.ThreadLocalRandom; + +public interface Probability { + /** + * The value of this Probability + * Should return a result between 0 and 1 (inclusive) + * A value of 1 or greater represents something that will always succeed + * A value of around 0.5 represents something that succeeds around half the time + * A value of 0 represents something that will always fail + * + * @return the value of probability + */ + double getValue(); + + /** + * Create a new Probability with the given value + * A value of 100 would represent 100% chance of success + * A value of 50 would represent 50% chance of success + * A value of 0 would represent 0% chance of success + * A value of 1 would represent 1% chance of success + * A value of 0.5 would represent 0.5% chance of success + * A value of 0.01 would represent 0.01% chance of success + * + * @param percentage the value of the probability + * @return a new Probability with the given value + */ + static @NotNull Probability ofPercent(double percentage) { + return new ProbabilityImpl(percentage); + } + + /** + * Simulates a "roll of the dice" + * If the value passed is higher than the "random" value, than it is a successful roll + * + * @param probabilityValue probability value + * @return true for succeeding, false for failing + */ + static private boolean isSuccessfulRoll(double probabilityValue) { + return (probabilityValue) >= ThreadLocalRandom.current().nextDouble(1D); + } + + /** + * Simulate an outcome on a probability and return true or false for the result of that outcome + * + * @return true if the probability succeeded, false if it failed + */ + default boolean evaluate() { + return isSuccessfulRoll(getValue()); + } + + /** + * Modify and then Simulate an outcome on a probability and return true or false for the result of that outcome + * + * @param probabilityMultiplier probability will be multiplied by this before success is checked + * @return true if the probability succeeded, false if it failed + */ + default boolean evaluate(double probabilityMultiplier) { + double probabilityValue = getValue() * probabilityMultiplier; + return isSuccessfulRoll(probabilityValue); + } +} diff --git a/src/main/java/com/gmail/nossr50/util/random/ProbabilityImpl.java b/src/main/java/com/gmail/nossr50/util/random/ProbabilityImpl.java new file mode 100644 index 000000000..e240f2f07 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/random/ProbabilityImpl.java @@ -0,0 +1,62 @@ +package com.gmail.nossr50.util.random; + +import com.gmail.nossr50.api.exceptions.ValueOutOfBoundsException; +import com.google.common.base.Objects; + +public class ProbabilityImpl implements Probability { + + private final double probabilityValue; + + /** + * Create a probability with a static value + * + * @param percentage the percentage value of the probability + */ + ProbabilityImpl(double percentage) throws ValueOutOfBoundsException { + if (percentage < 0) { + throw new ValueOutOfBoundsException("Value should never be negative for Probability! This suggests a coding mistake, contact the devs!"); + } + + // Convert to a 0-1 floating point representation + probabilityValue = percentage / 100.0D; + } + + ProbabilityImpl(double xPos, double xCeiling, double probabilityCeiling) throws ValueOutOfBoundsException { + if(probabilityCeiling > 100) { + throw new ValueOutOfBoundsException("Probability Ceiling should never be above 100!"); + } else if (probabilityCeiling < 0) { + throw new ValueOutOfBoundsException("Probability Ceiling should never be below 0!"); + } + + //Get the percent success, this will be from 0-100 + double probabilityPercent = (probabilityCeiling * (xPos / xCeiling)); + + //Convert to a 0-1 floating point representation + this.probabilityValue = probabilityPercent / 100.0D; + } + + @Override + public double getValue() { + return probabilityValue; + } + + @Override + public String toString() { + return "ProbabilityImpl{" + + "probabilityValue=" + probabilityValue + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ProbabilityImpl that = (ProbabilityImpl) o; + return Double.compare(that.probabilityValue, probabilityValue) == 0; + } + + @Override + public int hashCode() { + return Objects.hashCode(probabilityValue); + } +} diff --git a/src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java b/src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java new file mode 100644 index 000000000..bc757506c --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java @@ -0,0 +1,226 @@ +package com.gmail.nossr50.util.random; + +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillEvent; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.EventUtils; +import com.gmail.nossr50.util.Permissions; +import com.gmail.nossr50.util.player.UserManager; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.text.DecimalFormat; + +public class ProbabilityUtil { + public static final @NotNull DecimalFormat percent = new DecimalFormat("##0.00%"); + public static final double LUCKY_MODIFIER = 1.333D; + + /** + * Return a chance of success in "percentage" format, show to the player in UI elements + * + * @param player target player + * @param subSkillType target subskill + * @param isLucky whether to apply luck modifiers + * + * @return "percentage" representation of success + */ + public static double chanceOfSuccessPercentage(@NotNull Player player, + @NotNull SubSkillType subSkillType, + boolean isLucky) { + Probability probability = getSubSkillProbability(subSkillType, player); + //Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale + double percentageValue = probability.getValue(); //Doesn't need to be scaled + + //Apply lucky modifier + if(isLucky) { + percentageValue *= LUCKY_MODIFIER; + } + + return percentageValue; + } + + public static double chanceOfSuccessPercentage(@NotNull Probability probability, boolean isLucky) { + //Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale + double percentageValue = probability.getValue(); + + //Apply lucky modifier + if(isLucky) { + percentageValue *= LUCKY_MODIFIER; + } + + return percentageValue; + } + + static Probability getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance { + return switch (subSkillType) { + case AXES_ARMOR_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getImpactChance()); + case AXES_GREATER_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getGreaterImpactChance()); + case TAMING_FAST_FOOD_SERVICE -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getFastFoodChance()); + default -> throw new InvalidStaticChance(); + }; + } + + static SkillProbabilityType getProbabilityType(@NotNull SubSkillType subSkillType) { + SkillProbabilityType skillProbabilityType = SkillProbabilityType.DYNAMIC_CONFIGURABLE; + + if(subSkillType == SubSkillType.TAMING_FAST_FOOD_SERVICE + || subSkillType == SubSkillType.AXES_ARMOR_IMPACT + || subSkillType == SubSkillType.AXES_GREATER_IMPACT) + skillProbabilityType = SkillProbabilityType.STATIC_CONFIGURABLE; + + return skillProbabilityType; + } + + static @NotNull Probability ofSubSkill(@Nullable Player player, + @NotNull SubSkillType subSkillType) { + switch (getProbabilityType(subSkillType)) { + case DYNAMIC_CONFIGURABLE: + double probabilityCeiling; + double xCeiling; + double xPos; + + if (player != null) { + McMMOPlayer mmoPlayer = UserManager.getPlayer(player); + if (mmoPlayer == null) { + return Probability.ofPercent(0); + } + xPos = mmoPlayer.getSkillLevel(subSkillType.getParentSkill()); + } else { + xPos = 0; + } + + //Probability ceiling is configurable in this type + probabilityCeiling = mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType); + //The xCeiling is configurable in this type + xCeiling = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType); + return new ProbabilityImpl(xPos, xCeiling, probabilityCeiling); + case STATIC_CONFIGURABLE: + try { + return getStaticRandomChance(subSkillType); + } catch (InvalidStaticChance invalidStaticChance) { + invalidStaticChance.printStackTrace(); + } + default: + throw new RuntimeException("No case in switch statement for Skill Probability Type!"); + } + } + + /** + * This is one of several Skill RNG check methods + * This helper method is for specific {@link SubSkillType}, which help mcMMO understand where the RNG values used in our calculations come from this {@link SubSkillType} + *

+ * 1) Determine where the RNG values come from for the passed {@link SubSkillType} + * NOTE: In the config file, there are values which are static and which are more dynamic, this is currently a bit hardcoded and will need to be updated manually + *

+ * 2) Determine whether to use Lucky multiplier and influence the outcome + *

+ * 3) Creates a {@link Probability} and pipes it to {@link ProbabilityUtil} which processes the result and returns it + *

+ * This also calls a {@link SubSkillEvent} which can be cancelled, if it is cancelled this will return false + * The outcome of the probability can also be modified by this event that is called + * + * @param subSkillType target subskill + * @param player target player, can be null (null players are given odds equivalent to a player with no levels or luck) + * @return true if the Skill RNG succeeds, false if it fails + */ + public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) { + //Process probability + Probability probability = getSubSkillProbability(subSkillType, player); + + //Send out event + SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType); + + if(subSkillEvent.isCancelled()) { + return false; //Event got cancelled so this doesn't succeed + } + + //Result modifier + double resultModifier = subSkillEvent.getResultModifier(); + + //Mutate probability + if(resultModifier != 1.0D) + probability = Probability.ofPercent(probability.getValue() * resultModifier); + + //Luck + boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); + + if(isLucky) { + return probability.evaluate(LUCKY_MODIFIER); + } else { + return probability.evaluate(); + } + } + + /** + * This is one of several Skill RNG check methods + * This helper method is specific to static value RNG, which can be influenced by a player's Luck + * + * @param primarySkillType the related primary skill + * @param player the target player, can be null (null players have the worst odds) + * @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive) + * @return true if the RNG succeeds, false if it fails + */ + public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, double probabilityPercentage) { + //Grab a probability converted from a "percentage" value + Probability probability = Probability.ofPercent(probabilityPercentage); + + return isStaticSkillRNGSuccessful(primarySkillType, player, probability); + } + + /** + * This is one of several Skill RNG check methods + * This helper method is specific to static value RNG, which can be influenced by a player's Luck + * + * @param primarySkillType the related primary skill + * @param player the target player, can be null (null players have the worst odds) + * @param probability the probability of this player succeeding + * @return true if the RNG succeeds, false if it fails + */ + public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, @NotNull Probability probability) { + boolean isLucky = player != null && Permissions.lucky(player, primarySkillType); + + if(isLucky) { + return probability.evaluate(LUCKY_MODIFIER); + } else { + return probability.evaluate(); + } + } + + /** + * Skills activate without RNG, this allows other plugins to prevent that activation + * @param subSkillType target subskill + * @param player target player + * @return true if the skill succeeds (wasn't cancelled by any other plugin) + */ + public static boolean isNonRNGSkillActivationSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) { + return !EventUtils.callSubSkillEvent(player, subSkillType).isCancelled(); + } + + /** + * Grab the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player} + * + * @param subSkillType target subskill + * @param player target player + * @return the Probability of this skill succeeding + */ + public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType, @Nullable Player player) { + return ProbabilityUtil.ofSubSkill(player, subSkillType); + } + + public static @NotNull String[] getRNGDisplayValues(@NotNull Player player, @NotNull SubSkillType subSkill) { + double firstValue = chanceOfSuccessPercentage(player, subSkill, false); + double secondValue = chanceOfSuccessPercentage(player, subSkill, true); + + return new String[]{percent.format(firstValue), percent.format(secondValue)}; + } + + public static @NotNull String[] getRNGDisplayValues(@NotNull Probability probability) { + double firstValue = chanceOfSuccessPercentage(probability, false); + double secondValue = chanceOfSuccessPercentage(probability, true); + + return new String[]{percent.format(firstValue), percent.format(secondValue)}; + } +} diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceExecution.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceExecution.java deleted file mode 100644 index e5d51d740..000000000 --- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceExecution.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.gmail.nossr50.util.random; - -public interface RandomChanceExecution { - /** - * Gets the XPos used in the formula for success - * - * @return value of x for our success probability graph - */ - double getXPos(); - - /** - * The maximum odds for this RandomChanceExecution - * For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak - * - * @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed) - */ - double getProbabilityCap(); -} diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkill.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkill.java deleted file mode 100644 index 92d91ec83..000000000 --- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkill.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.gmail.nossr50.util.random; - -import com.gmail.nossr50.datatypes.player.McMMOPlayer; -import com.gmail.nossr50.datatypes.skills.SubSkillType; -import com.gmail.nossr50.util.Permissions; -import com.gmail.nossr50.util.player.UserManager; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class RandomChanceSkill implements RandomChanceExecution { - protected final double probabilityCap; - protected final boolean isLucky; - protected int skillLevel; - protected final double resultModifier; - protected final double maximumBonusLevelCap; - - public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) { - this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR; - - final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); - if (player != null && mcMMOPlayer != null) { - this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill()); - } else { - this.skillLevel = 0; - } - - if (player != null) - isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); - else - isLucky = false; - - this.resultModifier = resultModifier; - this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType); - } - - public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType) { - this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR; - - final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); - if (player != null && mcMMOPlayer != null) { - this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill()); - } else { - this.skillLevel = 0; - } - - if (player != null) - isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); - else - isLucky = false; - - this.resultModifier = 1.0D; - this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType); - } - - public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap) { - if (hasCap) - this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType); - else - this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR; - - final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); - if (player != null && mcMMOPlayer != null) { - this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill()); - } else { - this.skillLevel = 0; - } - - if (player != null) - isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); - else - isLucky = false; - - this.resultModifier = 1.0D; - this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType); - } - - public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, boolean luckyOverride) { - if (hasCap) - this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType); - else - this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR; - - final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); - if (player != null && mcMMOPlayer != null) { - this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill()); - } else { - this.skillLevel = 0; - } - - isLucky = luckyOverride; - - this.resultModifier = 1.0D; - this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType); - } - - public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, double resultModifier) { - if (hasCap) - this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType); - else - this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR; - - final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); - if (player != null && mcMMOPlayer != null) { - this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill()); - } else { - this.skillLevel = 0; - } - - if (player != null) - isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); - else - isLucky = false; - - this.resultModifier = resultModifier; - this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType); - } - - /** - * Gets the skill level of the player who owns this RandomChanceSkill - * - * @return the current skill level relating to this RandomChanceSkill - */ - public int getSkillLevel() { - return skillLevel; - } - - /** - * Modify the skill level used for this skill's RNG calculations - * - * @param newSkillLevel new skill level - */ - public void setSkillLevel(int newSkillLevel) { - skillLevel = newSkillLevel; - } - - /** - * The maximum bonus level for this skill - * This is when the skills level no longer increases the odds of success - * For example, a value of 25 will mean the success chance no longer grows after skill level 25 - * - * @return the maximum bonus from skill level for this skill - */ - public double getMaximumBonusLevelCap() { - return maximumBonusLevelCap; - } - - /** - * Gets the XPos used in the formula for success - * - * @return value of x for our success probability graph - */ - @Override - public double getXPos() { - return getSkillLevel(); - } - - /** - * The maximum odds for this RandomChanceExecution - * For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak - * - * @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed) - */ - @Override - public double getProbabilityCap() { - return probabilityCap; - } - - public boolean isLucky() { - return isLucky; - } - - public double getResultModifier() { - return resultModifier; - } -} diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkillStatic.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkillStatic.java deleted file mode 100644 index c96b71d6b..000000000 --- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkillStatic.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.gmail.nossr50.util.random; - -import com.gmail.nossr50.datatypes.skills.SubSkillType; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class RandomChanceSkillStatic extends RandomChanceSkill { - private final double xPos; - - public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType) { - super(player, subSkillType); - - this.xPos = xPos; - } - - public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType, boolean luckyOverride) { - super(player, subSkillType, false, luckyOverride); - - this.xPos = xPos; - } - - public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) { - super(player, subSkillType, resultModifier); - - this.xPos = xPos; - } - - /** - * Gets the XPos used in the formula for success - * - * @return value of x for our success probability graph - */ - @Override - public double getXPos() { - return xPos; - } - - /** - * The maximum odds for this RandomChanceExecution - * For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak - * - * @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed) - */ - @Override - public double getProbabilityCap() { - return probabilityCap; - } - - /** - * The maximum bonus level for this skill - * This is when the skills level no longer increases the odds of success - * For example, a value of 25 will mean the success chance no longer grows after skill level 25 - * - * @return the maximum bonus from skill level for this skill - */ - @Override - public double getMaximumBonusLevelCap() { - return 100; - } -} diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceStatic.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceStatic.java deleted file mode 100644 index 0b09a4a3c..000000000 --- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceStatic.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.gmail.nossr50.util.random; - -public class RandomChanceStatic implements RandomChanceExecution { - private final double xPos; - private final double probabilityCap; - private final boolean isLucky; - - public RandomChanceStatic(double xPos, double probabilityCap, boolean isLucky) { - this.xPos = xPos; - this.probabilityCap = probabilityCap; - this.isLucky = isLucky; - } - - /** - * Gets the XPos used in the formula for success - * - * @return value of x for our success probability graph - */ - @Override - public double getXPos() { - return xPos; - } - - /** - * The maximum odds for this RandomChanceExecution - * For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak - * - * @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed) - */ - @Override - public double getProbabilityCap() { - return probabilityCap; - } - - public boolean isLucky() { - return isLucky; - } -} diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceUtil.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceUtil.java deleted file mode 100644 index 0191172c1..000000000 --- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceUtil.java +++ /dev/null @@ -1,337 +0,0 @@ -package com.gmail.nossr50.util.random; - -import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -import com.gmail.nossr50.datatypes.skills.SubSkillType; -import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillEvent; -import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillRandomCheckEvent; -import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.util.EventUtils; -import com.gmail.nossr50.util.Permissions; -import com.gmail.nossr50.util.skills.SkillActivationType; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.text.DecimalFormat; -import java.util.concurrent.ThreadLocalRandom; - -public class RandomChanceUtil { - public static final @NotNull DecimalFormat percent = new DecimalFormat("##0.00%"); - //public static final DecimalFormat decimal = new DecimalFormat("##0.00"); - public static final double LINEAR_CURVE_VAR = 100.0D; - public static final double LUCKY_MODIFIER = 1.333D; - - /** - * This method is the final step in determining if a Sub-Skill / Secondary Skill in mcMMO successfully activates either from chance or otherwise - * Random skills check for success based on numbers and then fire a cancellable event, if that event is not cancelled they succeed - * non-RNG skills just fire the cancellable event and succeed if they go uncancelled - * - * @param skillActivationType this value represents what kind of activation procedures this sub-skill uses - * @param subSkillType The identifier for this specific sub-skill - * @param player The owner of this sub-skill - * @return returns true if all conditions are met and the event is not cancelled - */ - public static boolean isActivationSuccessful(@NotNull SkillActivationType skillActivationType, @NotNull SubSkillType subSkillType, @Nullable Player player) { - switch (skillActivationType) { - case RANDOM_LINEAR_100_SCALE_WITH_CAP: - return checkRandomChanceExecutionSuccess(player, subSkillType, true); - case RANDOM_STATIC_CHANCE: - return checkRandomStaticChanceExecutionSuccess(player, subSkillType); - case ALWAYS_FIRES: - SubSkillEvent event = EventUtils.callSubSkillEvent(player, subSkillType); - return !event.isCancelled(); - default: - return false; - } - } - - public static double getActivationChance(@NotNull SkillActivationType skillActivationType, @NotNull SubSkillType subSkillType, @Nullable Player player, boolean luckyOverride) { - switch (skillActivationType) { - case RANDOM_LINEAR_100_SCALE_WITH_CAP: - return getRandomChanceExecutionSuccess(player, subSkillType, true, luckyOverride); - case RANDOM_STATIC_CHANCE: - return getRandomStaticChanceExecutionSuccess(player, subSkillType, luckyOverride); - default: - return 0.1337; - } - } - - /** - * Checks whether or not the random chance succeeds - * - * @return true if the random chance succeeds - */ - public static boolean checkRandomChanceExecutionSuccess(@NotNull Player player, @NotNull PrimarySkillType primarySkillType, double chance) { - //Check the odds - chance *= 100; - - chance = addLuck(player, primarySkillType, chance); - - /* - * Stuff like treasures can specify a drop chance from 0.05 to 100 - * Because of that we need to use a large int bound and multiply the chance by 100 - */ - return rollDice(chance, 10000); - } - - public static boolean rollDice(double chanceOfSuccess, int bound) { - return rollDice(chanceOfSuccess, bound, 1.0F); - } - - public static boolean rollDice(double chanceOfSuccess, int bound, double resultModifier) { - return chanceOfSuccess > (ThreadLocalRandom.current().nextInt(bound) * resultModifier); - } - - /** - * Used for stuff like Excavation, Fishing, etc... - * - * @param randomChance - * @return - */ - public static boolean checkRandomChanceExecutionSuccess(@NotNull RandomChanceSkillStatic randomChance, double resultModifier) { - double chanceOfSuccess = calculateChanceOfSuccess(randomChance); - - //Check the odds - return rollDice(chanceOfSuccess, 100, resultModifier); - } - - /** - * Used for stuff like Excavation, Fishing, etc... - * - * @param randomChance - * @return - */ - public static boolean checkRandomChanceExecutionSuccess(@NotNull RandomChanceSkillStatic randomChance) { - return checkRandomChanceExecutionSuccess(randomChance, 1.0F); - } - - public static boolean checkRandomChanceExecutionSuccess(@NotNull RandomChanceSkill randomChance) { - double chanceOfSuccess = calculateChanceOfSuccess(randomChance); - - //Check the odds - return rollDice(chanceOfSuccess, 100); - } - - - /*public static double getRandomChanceExecutionChance(RandomChanceSkill randomChance) - { - double chanceOfSuccess = calculateChanceOfSuccess(randomChance); - return chanceOfSuccess; - }*/ - - /** - * Gets the Static Chance for something to activate - * - * @param randomChance - * @return - */ - public static double getRandomChanceExecutionChance(@NotNull RandomChanceExecution randomChance) { - return getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR); - } - - public static double getRandomChanceExecutionChance(@NotNull RandomChanceExecution randomChance, boolean luckyOverride) { - return getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR); - } - - public static double getRandomChanceExecutionChance(@NotNull RandomChanceStatic randomChance) { - double chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR); - - chanceOfSuccess = addLuck(randomChance.isLucky(), chanceOfSuccess); - - return chanceOfSuccess; - } - - /*private static double calculateChanceOfSuccess(RandomChanceStatic randomChance) { - double chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap()); - return chanceOfSuccess; - }*/ - - public static double calculateChanceOfSuccess(@NotNull RandomChanceSkill randomChance) { - double skillLevel = randomChance.getSkillLevel(); - double maximumProbability = randomChance.getProbabilityCap(); - double maximumBonusLevel = randomChance.getMaximumBonusLevelCap(); - - double chanceOfSuccess; - - if (skillLevel >= maximumBonusLevel) { - //Chance of success is equal to the maximum probability if the maximum bonus level has been reached - chanceOfSuccess = maximumProbability; - } else { - //Get chance of success - chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), maximumProbability, maximumBonusLevel); - } - - //Add Luck - chanceOfSuccess = addLuck(randomChance.isLucky(), chanceOfSuccess); - - return chanceOfSuccess; - } - - public static double calculateChanceOfSuccess(@NotNull RandomChanceSkillStatic randomChance) { - double chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), 100, 100); - - //Add Luck - chanceOfSuccess = addLuck(randomChance.isLucky(), chanceOfSuccess); - - return chanceOfSuccess; - } - - /** - * The formula for RNG success is determined like this - * maximum probability * ( x / maxlevel ) - * - * @return the chance of success from 0-100 (100 = guaranteed) - */ - private static int getChanceOfSuccess(double skillLevel, double maxProbability, double maxLevel) { - //return (int) (x / (y / LINEAR_CURVE_VAR)); - return (int) (maxProbability * (skillLevel / maxLevel)); - // max probability * (weight/maxlevel) = chance of success - } - - private static int getChanceOfSuccess(double x, double y) { - return (int) (x / (y / LINEAR_CURVE_VAR)); - // max probability * (weight/maxlevel) = chance of success - } - - public static double getRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap) { - RandomChanceSkill rcs = new RandomChanceSkill(player, subSkillType, hasCap); - return calculateChanceOfSuccess(rcs); - } - - public static double getRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, boolean luckyOverride) { - RandomChanceSkill rcs = new RandomChanceSkill(player, subSkillType, hasCap, luckyOverride); - return calculateChanceOfSuccess(rcs); - } - - public static double getRandomStaticChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean luckyOverride) { - try { - return getRandomChanceExecutionChance(new RandomChanceSkillStatic(getStaticRandomChance(subSkillType), player, subSkillType, luckyOverride)); - } catch (InvalidStaticChance invalidStaticChance) { - //Catch invalid static skills - invalidStaticChance.printStackTrace(); - } - - return 0.1337; //Puts on shades - } - - public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap) { - return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, hasCap)); - } - - public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType) { - return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType)); - } - - public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, double resultModifier) { - return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, hasCap, resultModifier)); - } - - public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) { - return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, resultModifier)); - } - - - public static boolean checkRandomStaticChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType) { - try { - return checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getStaticRandomChance(subSkillType), player, subSkillType)); - } catch (InvalidStaticChance invalidStaticChance) { - //Catch invalid static skills - invalidStaticChance.printStackTrace(); - } - - return false; - } - - /** - * Grabs static activation rolls for Secondary Abilities - * - * @param subSkillType The secondary ability to grab properties of - * @return The static activation roll involved in the RNG calculation - * @throws InvalidStaticChance if the skill has no defined static chance this exception will be thrown and you should know you're a naughty boy - */ - public static double getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance { - switch (subSkillType) { - case AXES_ARMOR_IMPACT: - return mcMMO.p.getAdvancedConfig().getImpactChance(); - case AXES_GREATER_IMPACT: - return mcMMO.p.getAdvancedConfig().getGreaterImpactChance(); - case TAMING_FAST_FOOD_SERVICE: - return mcMMO.p.getAdvancedConfig().getFastFoodChance(); - default: - throw new InvalidStaticChance(); - } - } - - public static boolean sendSkillEvent(Player player, SubSkillType subSkillType, double activationChance) { - SubSkillRandomCheckEvent event = new SubSkillRandomCheckEvent(player, subSkillType, activationChance); - return !event.isCancelled(); - } - - public static String @NotNull [] calculateAbilityDisplayValues(@NotNull SkillActivationType skillActivationType, @NotNull Player player, @NotNull SubSkillType subSkillType) { - double successChance = getActivationChance(skillActivationType, subSkillType, player, false); - double successChanceLucky = getActivationChance(skillActivationType, subSkillType, player, true); - - String[] displayValues = new String[2]; - - boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); - - displayValues[0] = percent.format(Math.min(successChance, 100.0D) / 100.0D); - displayValues[1] = isLucky ? percent.format(Math.min(successChanceLucky, 100.0D) / 100.0D) : null; - - return displayValues; - } - - public static String @NotNull [] calculateAbilityDisplayValuesStatic(@NotNull Player player, @NotNull PrimarySkillType primarySkillType, double chance) { - RandomChanceStatic rcs = new RandomChanceStatic(chance, LINEAR_CURVE_VAR, false); - double successChance = getRandomChanceExecutionChance(rcs); - - RandomChanceStatic rcs_lucky = new RandomChanceStatic(chance, LINEAR_CURVE_VAR, true); - double successChance_lucky = getRandomChanceExecutionChance(rcs_lucky); - - String[] displayValues = new String[2]; - - boolean isLucky = Permissions.lucky(player, primarySkillType); - - displayValues[0] = percent.format(Math.min(successChance, 100.0D) / 100.0D); - displayValues[1] = isLucky ? percent.format(Math.min(successChance_lucky, 100.0D) / 100.0D) : null; - - return displayValues; - } - - public static String @NotNull [] calculateAbilityDisplayValuesCustom(@NotNull SkillActivationType skillActivationType, @NotNull Player player, @NotNull SubSkillType subSkillType, double multiplier) { - double successChance = getActivationChance(skillActivationType, subSkillType, player, false); - double successChanceLucky = getActivationChance(skillActivationType, subSkillType, player, true); - //TODO: Most likely incorrectly displays the value for graceful roll but gonna ignore for now... - successChance *= multiplier; //Currently only used for graceful roll - String[] displayValues = new String[2]; - - boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill()); - - displayValues[0] = percent.format(Math.min(successChance, 100.0D) / 100.0D); - displayValues[1] = isLucky ? percent.format(Math.min(successChanceLucky, 100.0D) / 100.0D) : null; - - return displayValues; - } - - public static double addLuck(@NotNull Player player, @NotNull PrimarySkillType primarySkillType, double chance) { - if (Permissions.lucky(player, primarySkillType)) - return chance * LUCKY_MODIFIER; - else - return chance; - } - - public static double addLuck(boolean isLucky, double chance) { - if (isLucky) - return chance * LUCKY_MODIFIER; - else - return chance; - } - - public static double getMaximumProbability(@NotNull SubSkillType subSkillType) { - return mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType); - } - - public static double getMaxBonusLevelCap(@NotNull SubSkillType subSkillType) { - return mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType); - } -} diff --git a/src/main/java/com/gmail/nossr50/util/random/SkillProbabilityType.java b/src/main/java/com/gmail/nossr50/util/random/SkillProbabilityType.java new file mode 100644 index 000000000..95814f6a1 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/random/SkillProbabilityType.java @@ -0,0 +1,6 @@ +package com.gmail.nossr50.util.random; + +public enum SkillProbabilityType { + DYNAMIC_CONFIGURABLE, //Has multiple values used for calculation (taken from config files) + STATIC_CONFIGURABLE, //A single value used for calculations (taken from config files) +} diff --git a/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java index 1237ce086..4d2d51560 100644 --- a/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java +++ b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java @@ -11,7 +11,6 @@ import com.gmail.nossr50.events.scoreboard.ScoreboardEventReason; import com.gmail.nossr50.events.scoreboard.ScoreboardObjectiveEventReason; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import com.gmail.nossr50.skills.child.FamilyTree; import com.gmail.nossr50.util.LogUtils; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.player.NotificationManager; @@ -495,7 +494,7 @@ public class ScoreboardWrapper { sidebarObjective.getScore(ScoreboardManager.LABEL_REMAINING_XP).setScore(mcMMOPlayer.getXpToLevel(targetSkill) - currentXP); } else { - for (PrimarySkillType parentSkill : FamilyTree.getParents(targetSkill)) { + for (PrimarySkillType parentSkill : mcMMO.p.getSkillTools().getChildSkillParents(targetSkill)) { sidebarObjective.getScore(ScoreboardManager.skillLabels.get(parentSkill)).setScore(mcMMOPlayer.getSkillLevel(parentSkill)); } } diff --git a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java index 1d5eea33f..a361595e0 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java +++ b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java @@ -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); } } diff --git a/src/main/java/com/gmail/nossr50/util/skills/ProjectileUtils.java b/src/main/java/com/gmail/nossr50/util/skills/ProjectileUtils.java new file mode 100644 index 000000000..f64bac7a3 --- /dev/null +++ b/src/main/java/com/gmail/nossr50/util/skills/ProjectileUtils.java @@ -0,0 +1,84 @@ +package com.gmail.nossr50.util.skills; + +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.MetadataConstants; +import org.bukkit.block.BlockFace; +import org.bukkit.entity.Arrow; +import org.bukkit.metadata.FixedMetadataValue; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; + +public class ProjectileUtils { + public static Vector getNormal(BlockFace blockFace) { + return switch (blockFace) { + case UP -> new Vector(0, 1, 0); + case DOWN -> new Vector(0, -1, 0); + case NORTH -> new Vector(0, 0, -1); + case SOUTH -> new Vector(0, 0, 1); + case EAST -> new Vector(1, 0, 0); + case WEST -> new Vector(-1, 0, 0); + default -> new Vector(0, 0, 0); + }; + } + + /** + * Clean up all possible mcMMO related metadata for a projectile + * + * @param arrow projectile + */ + // TODO: Add test + public static void cleanupProjectileMetadata(@NotNull Arrow arrow) { + if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) { + arrow.removeMetadata(MetadataConstants.METADATA_KEY_INF_ARROW, mcMMO.p); + } + + if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) { + arrow.removeMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, mcMMO.p); + } + + if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) { + arrow.removeMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, mcMMO.p); + } + + if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW)) { + arrow.removeMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW, mcMMO.p); + } + + if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW)) { + arrow.removeMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW, mcMMO.p); + } + + if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT)) { + arrow.removeMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT, mcMMO.p); + } + } + + public static void copyArrowMetadata(@NotNull Plugin pluginRef, @NotNull Arrow arrowToCopy, @NotNull Arrow newArrow) { + if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) { + newArrow.setMetadata(MetadataConstants.METADATA_KEY_INF_ARROW, + arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_INF_ARROW).get(0)); + } + + if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) { + newArrow.setMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, + new FixedMetadataValue(pluginRef, + arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE).get(0).asDouble())); + } + + if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) { + newArrow.setMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, + arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE).get(0)); + } + + if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW)) { + newArrow.setMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW, + arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW).get(0)); + } + + if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW)) { + newArrow.setMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW, + arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW).get(0)); + } + } +} diff --git a/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java b/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java index ec4d56481..e06497128 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java +++ b/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java @@ -24,7 +24,7 @@ public class RankUtils { * * @param plugin plugin instance ref * @param mcMMOPlayer target player - * @param primarySkillType + * @param primarySkillType the skill to check * @param newLevel the new level of this skill */ public static void executeSkillUnlockNotifications(Plugin plugin, McMMOPlayer mcMMOPlayer, PrimarySkillType primarySkillType, int newLevel) @@ -55,6 +55,9 @@ public class RankUtils { } } + /** + * Reset the interval between skill unlock notifications + */ public static void resetUnlockDelayTimer() { count = 0; diff --git a/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java b/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java index 416926fe4..985f93bce 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java +++ b/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java @@ -30,6 +30,8 @@ public class SkillTools { public final @NotNull ImmutableSet EXACT_SUBSKILL_NAMES; public final @NotNull ImmutableList CHILD_SKILLS; public final static @NotNull ImmutableList NON_CHILD_SKILLS; + public final static @NotNull ImmutableList SALVAGE_PARENTS; + public final static @NotNull ImmutableList SMELTING_PARENTS; public final @NotNull ImmutableList COMBAT_SKILLS; public final @NotNull ImmutableList GATHERING_SKILLS; public final @NotNull ImmutableList MISC_SKILLS; @@ -50,9 +52,11 @@ public class SkillTools { } NON_CHILD_SKILLS = ImmutableList.copyOf(tempNonChildSkills); + SALVAGE_PARENTS = ImmutableList.of(PrimarySkillType.REPAIR, PrimarySkillType.FISHING); + SMELTING_PARENTS = ImmutableList.of(PrimarySkillType.MINING, PrimarySkillType.REPAIR); } - public SkillTools(@NotNull mcMMO pluginRef) { + public SkillTools(@NotNull mcMMO pluginRef) throws InvalidSkillException { this.pluginRef = pluginRef; /* @@ -140,26 +144,38 @@ public class SkillTools { */ List childSkills = new ArrayList<>(); -// List nonChildSkills = new ArrayList<>(); for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { if (isChildSkill(primarySkillType)) childSkills.add(primarySkillType); -// } { -// nonChildSkills.add(primarySkillType); -// } } CHILD_SKILLS = ImmutableList.copyOf(childSkills); -// NON_CHILD_SKILLS = ImmutableList.copyOf(nonChildSkills); /* * Build categorized skill lists */ - COMBAT_SKILLS = ImmutableList.of(PrimarySkillType.ARCHERY, PrimarySkillType.AXES, PrimarySkillType.SWORDS, PrimarySkillType.TAMING, PrimarySkillType.UNARMED); - GATHERING_SKILLS = ImmutableList.of(PrimarySkillType.EXCAVATION, PrimarySkillType.FISHING, PrimarySkillType.HERBALISM, PrimarySkillType.MINING, PrimarySkillType.WOODCUTTING); - MISC_SKILLS = ImmutableList.of(PrimarySkillType.ACROBATICS, PrimarySkillType.ALCHEMY, PrimarySkillType.REPAIR, PrimarySkillType.SALVAGE, PrimarySkillType.SMELTING); + COMBAT_SKILLS = ImmutableList.of( + PrimarySkillType.ARCHERY, + PrimarySkillType.AXES, + PrimarySkillType.CROSSBOWS, + PrimarySkillType.SWORDS, + PrimarySkillType.TAMING, + PrimarySkillType.TRIDENTS, + PrimarySkillType.UNARMED); + GATHERING_SKILLS = ImmutableList.of( + PrimarySkillType.EXCAVATION, + PrimarySkillType.FISHING, + PrimarySkillType.HERBALISM, + PrimarySkillType.MINING, + PrimarySkillType.WOODCUTTING); + MISC_SKILLS = ImmutableList.of( + PrimarySkillType.ACROBATICS, + PrimarySkillType.ALCHEMY, + PrimarySkillType.REPAIR, + PrimarySkillType.SALVAGE, + PrimarySkillType.SMELTING); /* * Build formatted/localized/etc string lists @@ -171,25 +187,18 @@ public class SkillTools { } private @NotNull PrimarySkillType getSuperAbilityParent(SuperAbilityType superAbilityType) throws InvalidSkillException { - switch(superAbilityType) { - case BERSERK: - return PrimarySkillType.UNARMED; - case GREEN_TERRA: - return PrimarySkillType.HERBALISM; - case TREE_FELLER: - return PrimarySkillType.WOODCUTTING; - case SUPER_BREAKER: - case BLAST_MINING: - return PrimarySkillType.MINING; - case SKULL_SPLITTER: - return PrimarySkillType.AXES; - case SERRATED_STRIKES: - return PrimarySkillType.SWORDS; - case GIGA_DRILL_BREAKER: - return PrimarySkillType.EXCAVATION; - default: - throw new InvalidSkillException("No parent defined for super ability! "+superAbilityType.toString()); - } + return switch (superAbilityType) { + case BERSERK -> PrimarySkillType.UNARMED; + case GREEN_TERRA -> PrimarySkillType.HERBALISM; + case TREE_FELLER -> PrimarySkillType.WOODCUTTING; + case SUPER_BREAKER, BLAST_MINING -> PrimarySkillType.MINING; + case SKULL_SPLITTER -> PrimarySkillType.AXES; + case SERRATED_STRIKES -> PrimarySkillType.SWORDS; + case GIGA_DRILL_BREAKER -> PrimarySkillType.EXCAVATION; + case SUPER_SHOTGUN -> PrimarySkillType.CROSSBOWS; + case TRIDENTS_SUPER_ABILITY -> PrimarySkillType.TRIDENTS; + case EXPLOSIVE_SHOT -> PrimarySkillType.ARCHERY; + }; } /** @@ -319,7 +328,6 @@ public class SkillTools { } public Set getSubSkills(PrimarySkillType primarySkillType) { - //TODO: Cache this! return primarySkillChildrenMap.get(primarySkillType); } @@ -329,14 +337,10 @@ public class SkillTools { // TODO: This is a little "hacky", we probably need to add something to distinguish child skills in the enum, or to use another enum for them public static boolean isChildSkill(PrimarySkillType primarySkillType) { - switch (primarySkillType) { - case SALVAGE: - case SMELTING: - return true; - - default: - return false; - } + return switch (primarySkillType) { + case SALVAGE, SMELTING -> true; + default -> false; + }; } /** @@ -401,34 +405,7 @@ public class SkillTools { * @return true if the player has permissions, false otherwise */ public boolean superAbilityPermissionCheck(SuperAbilityType superAbilityType, Player player) { - switch (superAbilityType) { - case BERSERK: - return Permissions.berserk(player); - - case BLAST_MINING: - return Permissions.remoteDetonation(player); - - case GIGA_DRILL_BREAKER: - return Permissions.gigaDrillBreaker(player); - - case GREEN_TERRA: - return Permissions.greenTerra(player); - - case SERRATED_STRIKES: - return Permissions.serratedStrikes(player); - - case SKULL_SPLITTER: - return Permissions.skullSplitter(player); - - case SUPER_BREAKER: - return Permissions.superBreaker(player); - - case TREE_FELLER: - return Permissions.treeFeller(player); - - default: - return false; - } + return superAbilityType.getPermissions(player); } public @NotNull List getChildSkills() { @@ -450,4 +427,17 @@ public class SkillTools { public @NotNull ImmutableList getMiscSkills() { return MISC_SKILLS; } + + public @NotNull ImmutableList getChildSkillParents(PrimarySkillType childSkill) + throws IllegalArgumentException { + switch (childSkill) { + case SALVAGE -> { + return SALVAGE_PARENTS; + } + case SMELTING -> { + return SMELTING_PARENTS; + } + default -> throw new IllegalArgumentException("Skill " + childSkill + " is not a child skill"); + } + } } diff --git a/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java b/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java index 6a15963ef..2012d6071 100644 --- a/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java +++ b/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java @@ -13,6 +13,7 @@ import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.metadata.ItemMetadataService; import com.gmail.nossr50.util.ItemUtils; import com.gmail.nossr50.util.Misc; +import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.text.StringUtils; @@ -33,6 +34,8 @@ import org.jetbrains.annotations.Nullable; import java.util.Iterator; +import static java.util.Objects.requireNonNull; + public final class SkillUtils { /** * This is a static utility class, therefore we don't want any instances of @@ -264,8 +267,8 @@ public final class SkillUtils { return false; } - - + + /** * Modify the durability of an ItemStack, using Armor specific formula for unbreaking enchant damage reduction * @@ -352,4 +355,14 @@ public final class SkillUtils { return quantity; } + + /** + * Checks if a player can use a skill + * @param player target player + * @param subSkillType target subskill + * @return true if the player has permission and has the skill unlocked + */ + public static boolean canUseSubskill(Player player, @NotNull SubSkillType subSkillType) { + return Permissions.isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType); + } } diff --git a/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java b/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java index 81ac8da28..65edc3d9e 100644 --- a/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java +++ b/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java @@ -133,38 +133,37 @@ public class TextComponentFactory { TextComponent.Builder webTextComponent; switch (webLinks) { - case WEBSITE: + case WEBSITE -> { webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL")); TextUtils.addChildWebComponent(webTextComponent, "Web"); webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlWebsite)); - break; - case SPIGOT: + } + case SPIGOT -> { webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL")); TextUtils.addChildWebComponent(webTextComponent, "Spigot"); webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlSpigot)); - break; - case DISCORD: + } + case DISCORD -> { webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL")); TextUtils.addChildWebComponent(webTextComponent, "Discord"); webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlDiscord)); - break; - case PATREON: + } + case PATREON -> { webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL")); TextUtils.addChildWebComponent(webTextComponent, "Patreon"); webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlPatreon)); - break; - case WIKI: + } + case WIKI -> { webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL")); TextUtils.addChildWebComponent(webTextComponent, "Wiki"); webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlWiki)); - break; - case HELP_TRANSLATE: + } + case HELP_TRANSLATE -> { webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL")); TextUtils.addChildWebComponent(webTextComponent, "Lang"); webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlTranslate)); - break; - default: - webTextComponent = Component.text().content("NOT DEFINED"); + } + default -> webTextComponent = Component.text().content("NOT DEFINED"); } TextUtils.addNewHoverComponentToTextComponent(webTextComponent, getUrlHoverEvent(webLinks)); @@ -177,44 +176,45 @@ public class TextComponentFactory { TextComponent.Builder componentBuilder = Component.text().content(webLinks.getNiceTitle()); switch (webLinks) { - case WEBSITE: + case WEBSITE -> { addUrlHeaderHover(webLinks, componentBuilder); componentBuilder.append(Component.newline()).append(Component.newline()); componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN)); componentBuilder.append(Component.text("\nDev Blogs, and information related to mcMMO can be found here", NamedTextColor.GRAY)); - break; - case SPIGOT: + } + case SPIGOT -> { addUrlHeaderHover(webLinks, componentBuilder); componentBuilder.append(Component.newline()).append(Component.newline()); componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN)); componentBuilder.append(Component.text("\nI post regularly in the discussion thread here!", NamedTextColor.GRAY)); - break; - case PATREON: + } + case PATREON -> { addUrlHeaderHover(webLinks, componentBuilder); componentBuilder.append(Component.newline()).append(Component.newline()); componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN)); componentBuilder.append(Component.newline()); componentBuilder.append(Component.text("Show support by buying me a coffee :)", NamedTextColor.GRAY)); - break; - case WIKI: + } + case WIKI -> { addUrlHeaderHover(webLinks, componentBuilder); componentBuilder.append(Component.newline()).append(Component.newline()); componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN)); componentBuilder.append(Component.newline()); componentBuilder.append(Component.text("I'm looking for more wiki staff, contact me on our discord!", NamedTextColor.DARK_GRAY)); - break; - case DISCORD: + } + case DISCORD -> { addUrlHeaderHover(webLinks, componentBuilder); componentBuilder.append(Component.newline()).append(Component.newline()); componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN)); - break; - case HELP_TRANSLATE: + } + case HELP_TRANSLATE -> { addUrlHeaderHover(webLinks, componentBuilder); componentBuilder.append(Component.newline()).append(Component.newline()); componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN)); componentBuilder.append(Component.newline()); componentBuilder.append(Component.text("You can use this website to help translate mcMMO into your language!" + "\nIf you want to know more contact me in discord.", NamedTextColor.DARK_GRAY)); + } } return componentBuilder.build(); @@ -474,7 +474,7 @@ public class TextComponentFactory { /* NEW SKILL SYSTEM */ for (AbstractSubSkill abstractSubSkill : InteractionManager.getSubSkillList()) { if (abstractSubSkill.getPrimarySkill() == parentSkill) { - if (Permissions.isSubSkillEnabled(player, abstractSubSkill)) + if (Permissions.isSubSkillEnabled(player, abstractSubSkill.getSubSkillType())) textComponents.add(TextComponentFactory.getSubSkillTextComponent(player, abstractSubSkill)); } } diff --git a/src/main/resources/advanced.yml b/src/main/resources/advanced.yml index 4e37db678..7d56c4e37 100644 --- a/src/main/resources/advanced.yml +++ b/src/main/resources/advanced.yml @@ -5,7 +5,7 @@ # For advanced users only! There is no need to change anything here. # # You can customize almost every aspect of every skill here. -# Its mainly here if you've customized the experience formula. +# It's mainly here if you've customized the experience formula. # Configure at what level you get better with certain abilities. # ##### @@ -20,7 +20,7 @@ Feedback: PlayerTips: true SkillCommand: BlankLinesAboveHeader: true - # If sendtitles is true messages will be sent using the title api (BIG TEXT ON SCREEN) + # If sendtitles is true, messages will be sent using the title api (BIG TEXT ON SCREEN) Events: XP: SendTitles: true @@ -264,6 +264,11 @@ Skills: MaxBonusLevel: Standard: 100 RetroMode: 1000 + VerdantBounty: + ChanceMax: 50.0 + MaxBonusLevel: + Standard: 1000 + RetroMode: 10000 HylianLuck: # ChanceMax: Maximum chance of Hylian Luck when on or higher @@ -284,6 +289,11 @@ Skills: # Settings for Mining ### Mining: + MotherLode: + MaxBonusLevel: + Standard: 1000 + RetroMode: 10000 + ChanceMax: 50.0 SuperBreaker: AllowTripleDrops: true DoubleDrops: @@ -608,9 +618,16 @@ Skills: Knock_On_Wood: Add_XP_Orbs_To_Drops: true + # Triple Drops + CleanCuts: + # ChanceMax: Maximum chance of receiving triple drops (100 = 100%) + # MaxBonusLevel: Level when the maximum chance of receiving triple drops is reached + ChanceMax: 50.0 + MaxBonusLevel: + Standard: 1000 + RetroMode: 10000 # Double Drops HarvestLumber: - # ChanceMax & MaxBonusLevel are only used for Classic, I'll make that more clear in the future. # ChanceMax: Maximum chance of receiving double drops (100 = 100%) # MaxBonusLevel: Level when the maximum chance of receiving double drops is reached ChanceMax: 100.0 diff --git a/src/main/resources/chat.yml b/src/main/resources/chat.yml index 34f812a07..febf9c9f6 100644 --- a/src/main/resources/chat.yml +++ b/src/main/resources/chat.yml @@ -8,10 +8,12 @@ Chat: Enable: true # Whether to use the current display name of a player Use_Display_Names: true + Send_To_Console: true Spies: # Whether players with the chat spy permission join the server with chat spying toggled on Automatically_Enable_Spying: false Admin: + Send_To_Console: true # Enable or disable admin chat Enable: true # Whether to use the current display name of a player diff --git a/src/main/resources/child.yml b/src/main/resources/child.yml deleted file mode 100644 index 685a6ce71..000000000 --- a/src/main/resources/child.yml +++ /dev/null @@ -1,16 +0,0 @@ -# -# mcMMO child skill configuration -# Last updated on ${project.version}-b${BUILD_NUMBER} -# -# You do not need to modify this file except to change parents of child skills -# -# If you wish a child skill to be the parent of another child skill, you must also make your changes to the child.yml within the jar -# WARNING: THIS IS NOT SUPPORTED, IF YOU DO SO YOU ARE RESPONSIBLE FOR THE ISSUES THAT MAY ARISE. That said, watch out for circular dependencies, those are bad. -# -##### -Salvage: - - Fishing - - Repair -Smelting: - - Mining - - Repair \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 789729cc6..4df9d2eb0 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -49,6 +49,10 @@ General: RetroMode: Enabled: true Locale: en_US + PowerLevel: + Skill_Mastery: + Enabled: true + AprilFoolsEvent: true MOTD_Enabled: true EventBroadcasts: true EventInfoOnPlayerJoin: true diff --git a/src/main/resources/experience.yml b/src/main/resources/experience.yml index 00e72f01d..e83fd3770 100644 --- a/src/main/resources/experience.yml +++ b/src/main/resources/experience.yml @@ -77,6 +77,10 @@ Experience_Bars: Enable: true Color: BLUE BarStyle: SEGMENTED_6 + Crossbows: + Enable: true + Color: BLUE + BarStyle: SEGMENTED_6 Excavation: Enable: true Color: YELLOW @@ -113,6 +117,10 @@ Experience_Bars: Enable: true Color: RED BarStyle: SEGMENTED_6 + Tridents: + Enable: true + Color: BLUE + BarStyle: SEGMENTED_6 Unarmed: Enable: true Color: BLUE @@ -159,8 +167,10 @@ Experience_Formula: Breeding: Multiplier: 1.0 - # Experience gained will get divided by these values. 1.0 by default, 2.0 means two times less XP gained. - Modifier: + # Experience gained will get multiplied by these values. 1.0 by default, 0.5 means half XP gained. This happens right before multiplying the XP by the global multiplier. + Skill_Multiplier: + Crossbows: 1.0 + Tridents: 1.0 Swords: 1.0 Taming: 1.0 Acrobatics: 1.0 diff --git a/src/main/resources/locale/locale_en_US.properties b/src/main/resources/locale/locale_en_US.properties index 27c45d0d9..84316e4c5 100644 --- a/src/main/resources/locale/locale_en_US.properties +++ b/src/main/resources/locale/locale_en_US.properties @@ -21,6 +21,7 @@ JSON.Acrobatics=Acrobatics JSON.Alchemy=Alchemy JSON.Archery=Archery JSON.Axes=Axes +JSON.Crossbows=Crossbows JSON.Excavation=Excavation JSON.Fishing=Fishing JSON.Herbalism=Herbalism @@ -29,6 +30,7 @@ JSON.Repair=Repair JSON.Salvage=Salvage JSON.Swords=Swords JSON.Taming=Taming +JSON.Tridents=Tridents JSON.Unarmed=Unarmed JSON.Woodcutting=Woodcutting JSON.URL.Website=The official mcMMO Website! @@ -88,6 +90,7 @@ Overhaul.Name.Acrobatics=Acrobatics Overhaul.Name.Alchemy=Alchemy Overhaul.Name.Archery=Archery Overhaul.Name.Axes=Axes +Overhaul.Name.Crossbows=Crossbows Overhaul.Name.Excavation=Excavation Overhaul.Name.Fishing=Fishing Overhaul.Name.Herbalism=Herbalism @@ -97,6 +100,7 @@ Overhaul.Name.Salvage=Salvage Overhaul.Name.Smelting=Smelting Overhaul.Name.Swords=Swords Overhaul.Name.Taming=Taming +Overhaul.Name.Tridents=Tridents Overhaul.Name.Unarmed=Unarmed Overhaul.Name.Woodcutting=Woodcutting # /mcMMO Command Style Stuff @@ -112,6 +116,7 @@ XPBar.Acrobatics=Acrobatics Lv.&6{0} XPBar.Alchemy=Alchemy Lv.&6{0} XPBar.Archery=Archery Lv.&6{0} XPBar.Axes=Axes Lv.&6{0} +XPBar.Crossbows=Crossbows Lv.&6{0} XPBar.Excavation=Excavation Lv.&6{0} XPBar.Fishing=Fishing Lv.&6{0} XPBar.Herbalism=Herbalism Lv.&6{0} @@ -121,6 +126,7 @@ XPBar.Salvage=Salvage Lv.&6{0} XPBar.Smelting=Smelting Lv.&6{0} XPBar.Swords=Swords Lv.&6{0} XPBar.Taming=Taming Lv.&6{0} +XPBar.Tridents=Tridents Lv.&6{0} XPBar.Unarmed=Unarmed Lv.&6{0} XPBar.Woodcutting=Woodcutting Lv.&6{0} #This is just a preset template that gets used if the 'ExtraDetails' setting is turned on in experience.yml (off by default), you can ignore this template and just edit the strings above @@ -176,6 +182,13 @@ Archery.SubSkill.ArcheryLimitBreak.Description=Breaking your limits. Increased d Archery.SubSkill.ArcheryLimitBreak.Stat=Limit Break Max DMG Archery.Listener=Archery: Archery.SkillName=ARCHERY +Archery.SubSkill.ExplosiveShot.Name=Explosive Shot +Archery.SubSkill.ExplosiveShot.Description=Fire an explosive arrow +Archery.Skills.ExplosiveShot.Off= +Archery.Skills.ExplosiveShot.On=&a**EXPLOSIVE SHOT ACTIVATED** +Archery.Skills.ExplosiveShot.Other.Off=Explosive Shot&a has worn off for &e{0} +Archery.Skills.ExplosiveShot.Other.On=&a{0}&2 has used &cExplosive Shot! +Archery.Skills.ExplosiveShot.Refresh=&aYour &Explosive Shot &ability is refreshed! #AXES Axes.Ability.Bonus.0=Axe Mastery Axes.Ability.Bonus.1=Bonus {0} damage @@ -285,8 +298,11 @@ Herbalism.SubSkill.FarmersDiet.Name=Farmer's Diet Herbalism.SubSkill.FarmersDiet.Description=Improves hunger restored from farmed foods Herbalism.SubSkill.FarmersDiet.Stat=Farmer's Diet: &aRank {0} Herbalism.SubSkill.DoubleDrops.Name=Double Drops -Herbalism.SubSkill.DoubleDrops.Description=Double the normal loot +Herbalism.SubSkill.DoubleDrops.Description=Skillfully harvest double the loot Herbalism.SubSkill.DoubleDrops.Stat=Double Drop Chance +Herbalism.SubSkill.VerdantBounty.Name=Verdant Bounty +Herbalism.SubSkill.VerdantBounty.Description=Masterfully harvest triple the loot +Herbalism.SubSkill.VerdantBounty.Stat=Triple Drop Chance Herbalism.SubSkill.HylianLuck.Name=Hylian Luck Herbalism.SubSkill.HylianLuck.Description=Gives a small chance of finding rare items Herbalism.SubSkill.HylianLuck.Stat=Hylian Luck Chance @@ -311,8 +327,11 @@ Mining.SubSkill.SuperBreaker.Name=Super Breaker Mining.SubSkill.SuperBreaker.Description=Speed+, Triple Drop Chance Mining.SubSkill.SuperBreaker.Stat=Super Breaker Length Mining.SubSkill.DoubleDrops.Name=Double Drops -Mining.SubSkill.DoubleDrops.Description=Double the normal loot +Mining.SubSkill.DoubleDrops.Description=Skillfully mine double the loot Mining.SubSkill.DoubleDrops.Stat=Double Drop Chance +Mining.SubSkill.MotherLode.Name=Mother Lode +Mining.SubSkill.MotherLode.Description=Masterfully mine triple the loot +Mining.SubSkill.MotherLode.Stat=Triple Drop Chance Mining.SubSkill.BlastMining.Name=Blast Mining Mining.SubSkill.BlastMining.Description=Bonuses to mining with TNT Mining.SubSkill.BlastMining.Stat=Blast Mining:&a Rank {0}/{1} &7({2}) @@ -395,7 +414,7 @@ Salvage.Skills.Adept.Level=You must be level &e{0}&c to salvage &e{1} Salvage.Skills.TooDamaged=&4This item is too damaged to be salvaged. Salvage.Skills.ArcaneFailed=&cYou were unable to extract the knowledge contained within this item. Salvage.Skills.ArcanePartial=&cYou were only able to extract some of the knowledge contained within this item. -Salvage.Skills.ArcaneSuccess=&aYou able to extract all of the knowledge contained within this item! +Salvage.Skills.ArcaneSuccess=&aYou were able to extract all the knowledge contained within this item! Salvage.Listener.Anvil=&4You have placed a Salvage anvil, use this to Salvage tools and armor. Salvage.Listener=Salvage: Salvage.SkillName=SALVAGE @@ -404,6 +423,51 @@ Salvage.Skills.Lottery.Perfect=&a&lPerfect!&r&6 You salvaged &3{1}&6 effortlessl Salvage.Skills.Lottery.Untrained=&7You aren't properly trained in salvaging. You were only able to recover &c{0}&7 materials from &a{1}&7. #Anvil (Shared between SALVAGE and REPAIR) Anvil.Unbreakable=This item is unbreakable! +#CROSSBOWS +Crossbows.SkillName=CROSSBOWS +Crossbows.Ability.Lower=&7You lower your crossbow. +Crossbows.Ability.Ready=&3You &6ready&3 your Crossbow. +Crossbows.Skills.SSG.Refresh=&aYour &eSuper Shotgun &aability is refreshed! +Crossbows.Skills.SSG.Other.On=&a{0}&2 used &Super Shotgun! +Crossbows.SubSkill.PoweredShot.Name=Powered Shot +Crossbows.SubSkill.PoweredShot.Description=Increases damage done with crossbows +Crossbows.SubSkill.PoweredShot.Stat=Powered Shot Bonus Damage +Crossbows.SubSkill.CrossbowsLimitBreak.Name=Crossbows Limit Break +Crossbows.SubSkill.CrossbowsLimitBreak.Description=Breaking your limits. Increased damage against tough opponents. Intended for PVP, up to server settings for whether it will boost damage in PVE. +Crossbows.SubSkill.CrossbowsLimitBreak.Stat=Limit Break Max DMG +Crossbows.SubSkill.TrickShot.Name=Trick Shot +Crossbows.SubSkill.TrickShot.Description=Richochet arrows with steep angles +Crossbows.SubSkill.TrickShot.Stat=Trick Shot Max Bounces +Crossbows.SubSkill.TrickShot.Stat.Extra=Trick Shot Max Bounces: &a{0} +Crossbows.SubSkill.TrickShot.Stat.Extra2=Trick Shot Reduced DMG per Bounce: &a{0} +Crossbows.SubSkill.SuperShotgun.Name=Super Shotgun +Crossbows.Listener=Crossbows: + +#TRIDENTS +Tridents.SkillName=TRIDENTS +Tridents.Ability.Lower=&7You lower your trident. +Tridents.Ability.Ready=&3You &6ready&3 your Trident. +Tridents.SubSkill.Impale.Name=Impale +Tridents.SubSkill.Impale.Description=Increases damage done with tridents +Tridents.SubSkill.Impale.Stat=Impale Bonus Damage +Tridents.SubSkill.TridentsLimitBreak.Name=Tridents Limit Break +Tridents.SubSkill.TridentsLimitBreak.Description=Breaking your limits. Increased damage against tough opponents. Intended for PVP, up to server settings for whether it will boost damage in PVE. +Tridents.SubSkill.TridentsLimitBreak.Stat=Limit Break Max DMG +Tridents.SubSkill.TridentAbility.Name=WIP +Tridents.Listener=Tridents: + +#MACES +Maces.SkillName=MACES +Maces.Ability.Lower=&7You lower your mace. +Maces.Ability.Ready=&3You &6ready&3 your Mace. +Maces.Skills.MaceSmash.Refresh=&aYour &eGiga Smash &aability is refreshed! +Maces.Skills.MaceSmash.Other.On=&a{0}&2 used &cGiga Smash! +Maces.SubSkill.GigaSmash.Name=Giga Smash +Maces.SubSkill.MacesLimitBreak.Name=Maces Limit Break +Maces.SubSkill.MacesLimitBreak.Description=Breaking your limits. Increased damage against tough opponents. Intended for PVP, up to server settings for whether it will boost damage in PVE. +Maces.SubSkill.MacesLimitBreak.Stat=Limit Break Max DMG +Maces.Listener=Maces: + #SWORDS Swords.Ability.Lower=&7You lower your sword. Swords.Ability.Ready=&3You &6ready&3 your Sword. @@ -542,8 +606,11 @@ Woodcutting.SubSkill.KnockOnWood.Stat=Knock on Wood Woodcutting.SubSkill.KnockOnWood.Loot.Normal=Standard loot from trees Woodcutting.SubSkill.KnockOnWood.Loot.Rank2=Standard loot from trees and experience orbs Woodcutting.SubSkill.HarvestLumber.Name=Harvest Lumber -Woodcutting.SubSkill.HarvestLumber.Description=Skillfully extract more Lumber +Woodcutting.SubSkill.HarvestLumber.Description=Skillfully extract up to double the Lumber Woodcutting.SubSkill.HarvestLumber.Stat=Double Drop Chance +Woodcutting.SubSkill.CleanCuts.Name=Clean Cuts +Woodcutting.SubSkill.CleanCuts.Description=Masterfully extract up to triple the Lumber +Woodcutting.SubSkill.CleanCuts.Stat=Triple Drop Chance Woodcutting.SubSkill.Splinter.Name=Splinter Woodcutting.SubSkill.Splinter.Description=Cut down trees more efficiently. Woodcutting.SubSkill.BarkSurgeon.Name=Bark Surgeon @@ -830,6 +897,7 @@ Commands.XPGain.Alchemy=Brewing Potions Commands.XPGain.Archery=Attacking Monsters Commands.XPGain.Axes=Attacking Monsters Commands.XPGain.Child=Gains levels from Parent Skills +Commands.XPGain.Crossbows=Attacking Monsters Commands.XPGain.Excavation=Digging and finding treasures Commands.XPGain.Fishing=Fishing (Go figure!) Commands.XPGain.Herbalism=Harvesting Herbs @@ -837,6 +905,7 @@ Commands.XPGain.Mining=Mining Stone & Ore Commands.XPGain.Repair=Repairing Commands.XPGain.Swords=Attacking Monsters Commands.XPGain.Taming=Animal Taming, or combat w/ your wolves +Commands.XPGain.Tridents=Attacking Monsters Commands.XPGain.Unarmed=Attacking Monsters Commands.XPGain.Woodcutting=Chopping down trees Commands.XPGain=&8XP GAIN: &f{0} @@ -970,6 +1039,13 @@ Guides.Woodcutting.Section.0=&3About Woodcutting:\n&eWoodcutting is all about ch Guides.Woodcutting.Section.1=&3How does Tree Feller work?\n&eTree Feller is an active ability, you can right-click\n&ewhile holding an ax to activate Tree Feller. This will\n&ecause the entire tree to break instantly, dropping all\n&eof its logs at once. Guides.Woodcutting.Section.2=&3How does Leaf Blower work?\n&eLeaf Blower is a passive ability that will cause leaf\n&eblocks to break instantly when hit with an axe. By default,\nðis ability unlocks at level 100. Guides.Woodcutting.Section.3=&3How do Double Drops work?\n&eThis passive ability gives you a chance to obtain an extra\n&eblock for every log you chop. +# Crossbows +Guides.Crossbows.Section.0=&3About Crossbows:\n&eCrossbows is all about shooting with your crossbow.\n\n&3XP GAIN:\n&eXP is gained whenever you shoot mobs with a crossbow. +Guides.Crossbows.Section.1=&3How does Trickshot work?\n&eTrickshot is an passive ability, you shoot your bolts at a shallow angle with a crossbow to attempt a Trickshot. This will cause the arrow to ricochet off of blocks and potentially hit a target. The number of potential bounces from a ricochet depend on the rank of Trickshot. +# Tridents +Guides.Tridents.Section.0=&3About Tridents:\n&eTridents skill involves impaling foes with your trident.\n\n&3XP GAIN:\n&eXP is gained whenever you hit mobs with a trident. + + #INSPECT Inspect.Offline= &cYou do not have permission to inspect offline players! Inspect.OfflineStats=mcMMO Stats for Offline Player &e{0} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a8b56712c..198d465fc 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -20,9 +20,6 @@ folia-supported: true api-version: 1.13 commands: -# mmodroptreasures: -# description: An admin command used to spawn treasure drops -# permission: mcmmo.commands.droptreasures mmoxpbar: aliases: xpbarsettings description: Change XP bar settings @@ -124,12 +121,18 @@ commands: archery: description: Detailed mcMMO skill info permission: mcmmo.commands.archery + crossbows: + description: Detailed mcMMO skill info + permission: mcmmo.commands.crossbows swords: description: Detailed mcMMO skill info permission: mcmmo.commands.swords taming: description: Detailed mcMMO skill info permission: mcmmo.commands.taming + tridents: + description: Detailed mcMMO skill info + permission: mcmmo.commands.tridents unarmed: description: Detailed mcMMO skill info permission: mcmmo.commands.unarmed @@ -151,6 +154,10 @@ commands: salvage: description: Detailed mcMMO skill info permission: mcmmo.commands.salvage + mmopower: + description: Shows skill mastery and power level info + permission: mcmmo.commands.mmopower + aliases: [mmopowerlevel, powerlevel] adminchat: aliases: [ac, a] description: Toggle Admin chat or send admin chat messages @@ -234,6 +241,7 @@ permissions: mcmmo.ability.alchemy.all: true mcmmo.ability.archery.all: true mcmmo.ability.axes.all: true + mcmmo.ability.crossbows.all: true mcmmo.ability.excavation.all: true mcmmo.ability.fishing.all: true mcmmo.ability.herbalism.all: true @@ -243,6 +251,7 @@ permissions: mcmmo.ability.smelting.all: true mcmmo.ability.swords.all: true mcmmo.ability.taming.all: true + mcmmo.ability.tridents.all: true mcmmo.ability.unarmed.all: true mcmmo.ability.woodcutting.all: true mcmmo.ability.acrobatics.*: @@ -281,12 +290,15 @@ permissions: mcmmo.ability.archery.all: description: Allows access to all Archery abilities children: + mcmmo.ability.archery.explosiveshot: true mcmmo.ability.archery.skillshot: true mcmmo.ability.archery.daze: true mcmmo.ability.archery.arrowretrieval: true mcmmo.ability.archery.archerylimitbreak: true + mcmmo.ability.archery.explosiveshot: + description: Allows access to the Explosive Shot super ability for Archery mcmmo.ability.archery.archerylimitbreak: - description: Adds damage to bows and crossbows + description: Adds damage to bows mcmmo.ability.archery.skillshot: description: Allows bonus damage from the Archery SkillShot ability mcmmo.ability.archery.daze: @@ -319,6 +331,22 @@ permissions: description: Allows access to the Impact ability mcmmo.ability.axes.skullsplitter: description: Allows access to the Skull Splitter ability + mcmmo.ability.crossbows.*: + description: Allows access to all Crossbows abilities + children: + mcmmo.ability.crossbows.all: true + mcmmo.ability.crossbows.all: + description: Allows access to all Crossbows abilities + children: + mcmmo.ability.crossbows.trickshot: true + mcmmo.ability.crossbows.poweredshot: true + mcmmo.ability.crossbows.crossbowslimitbreak: true + mcmmo.ability.crossbows.crossbowslimitbreak: + description: Adds damage to crossbows + mcmmo.ability.crossbows.trickshot: + description: Allows access to the Trick Shot ability + mcmmo.ability.crossbows.poweredshot: + description: Allows access to the Powered Shot ability mcmmo.ability.excavation.*: default: false description: Allows access to all Excavation abilities @@ -376,6 +404,9 @@ permissions: mcmmo.ability.herbalism.greenthumb.all: true mcmmo.ability.herbalism.hylianluck: true mcmmo.ability.herbalism.shroomthumb: true + mcmmo.ability.herbalism.verdantbounty: true + mcmmo.ability.herbalism.verdantbounty: + description: Allows access to end game progression for Herbalism mcmmo.ability.herbalism.doubledrops: description: Allows double drop chance from Herbalism mcmmo.ability.herbalism.farmersdiet: @@ -456,6 +487,7 @@ permissions: mcmmo.ability.mining.blastmining.all: true mcmmo.ability.mining.doubledrops: true mcmmo.ability.mining.superbreaker: true + mcmmo.ability.mining.motherlode: true mcmmo.ability.mining.blastmining.*: default: false description: Allows access to all Blast Mining abilities @@ -473,6 +505,8 @@ permissions: description: Allows access to the Demolitions Expertise ability mcmmo.ability.mining.blastmining.detonate: description: Allows for remote TNT detonation + mcmmo.ability.mining.motherlode: + description: Allows access to mother lode subskill mcmmo.ability.mining.doubledrops: description: Allows double drop chance when mining mcmmo.ability.mining.superbreaker: @@ -677,6 +711,20 @@ permissions: description: Allows access to the Thick Fur ability mcmmo.ability.taming.pummel: description: Allows access to the Pummel ability + mcmmo.ability.tridents.*: + default: false + description: Allows access to all Trident abilities + children: + mcmmo.ability.tridents.all: true + mcmmo.ability.tridents.all: + description: Allows access to all Trident abilities + children: + mcmmo.ability.tridents.impale: true + mcmmo.ability.tridents.tridentslimitbreak: true + mcmmo.ability.tridents.impale: + description: Allows access to tridents Impale ability + mcmmo.ability.tridents.tridentslimitbreak: + description: Adds damage to tridents mcmmo.ability.unarmed.*: default: false description: Allows access to all Unarmed abilities @@ -721,6 +769,9 @@ permissions: mcmmo.ability.woodcutting.knockonwood: true mcmmo.ability.woodcutting.leafblower: true mcmmo.ability.woodcutting.treefeller: true + mcmmo.ability.woodcutting.cleancuts: true + mcmmo.ability.woodcutting.cleancuts: + description: Allows access to end game progression for Woodcutting mcmmo.ability.woodcutting.knockonwood: description: Allows access to Knock on Wood subskill mcmmo.ability.woodcutting.splinter: @@ -802,6 +853,8 @@ permissions: mcmmo.commands.alchemy: true mcmmo.commands.archery: true mcmmo.commands.axes: true + mcmmo.commands.crossbows: true + mcmmo.commands.tridents: true mcmmo.commands.excavation: true mcmmo.commands.fishing: true mcmmo.commands.herbalism: true @@ -824,6 +877,7 @@ permissions: mcmmo.commands.taming: true mcmmo.commands.unarmed: true mcmmo.commands.woodcutting: true + mcmmo.commands.mmopower: true mcmmo.commands.defaultsop: description: Implies all default op mcmmo.commands permissions. children: @@ -871,29 +925,16 @@ permissions: description: Allows access to the archery command mcmmo.commands.axes: description: Allows access to the axes command + mcmmo.commands.crossbows: + description: Allows access to the crossbows command mcmmo.commands.excavation: description: Allows access to the excavation command mcmmo.commands.fishing: description: Allows access to the fishing command -# mcmmo.commands.hardcore.*: -# default: false -# description: Implies access to all mcmmo.commands.hardcore permissions -# children: -# mcmmo.commands.hardcore.all: true -# mcmmo.commands.hardcore.all: -# description: Implies access to all mcmmo.commands.hardcore permissions -# children: -# mcmmo.commands.hardcore: true -# mcmmo.commands.hardcore.modify: true -# mcmmo.commands.hardcore.toggle: true -# mcmmo.commands.hardcore: -# description: Allows access to the hardcore command -# mcmmo.commands.hardcore.modify: -# description: Allows access to the hardcore command to modify the hardcore rate -# mcmmo.commands.hardcore.toggle: -# description: Allows access to the hardcore command to toggle hardcore on/off mcmmo.commands.herbalism: description: Allows access to the herbalism command + mcmmo.commands.tridents: + description: Allows access to the tridents command mcmmo.commands.inspect.*: default: false description: Implies access to all mcmmo.commands.inspect permissions @@ -999,6 +1040,7 @@ permissions: mcmmo.commands.mctop.alchemy: true mcmmo.commands.mctop.archery: true mcmmo.commands.mctop.axes: true + mcmmo.commands.mctop.crossbows: true mcmmo.commands.mctop.excavation: true mcmmo.commands.mctop.fishing: true mcmmo.commands.mctop.herbalism: true @@ -1008,6 +1050,7 @@ permissions: mcmmo.commands.mctop.smelting: true mcmmo.commands.mctop.swords: true mcmmo.commands.mctop.taming: true + mcmmo.commands.mctop.tridents: true mcmmo.commands.mctop.unarmed: true mcmmo.commands.mctop.woodcutting: true mcmmo.commands.mctop: @@ -1020,6 +1063,8 @@ permissions: description: Allows access to the mctop command for archery mcmmo.commands.mctop.axes: description: Allows access to the mctop command for axes + mcmmo.commands.mctop.crossbows: + description: Allows access to the mctop command for crossbows mcmmo.commands.mctop.excavation: description: Allows access to the mctop command for excavation mcmmo.commands.mctop.fishing: @@ -1038,6 +1083,8 @@ permissions: description: Allows access to the mctop command for swords mcmmo.commands.mctop.taming: description: Allows access to the mctop command for taming + mcmmo.commands.mctop.tridents: + description: Allows access to the mctop command for tridents mcmmo.commands.mctop.unarmed: description: Allows access to the mctop command for unarmed mcmmo.commands.mctop.woodcutting: @@ -1452,6 +1499,9 @@ permissions: mcmmo.perks.lucky.axes: default: false description: Gives Axes abilities & skills a 33.3% better chance to activate. + mcmmo.perks.lucky.crossbows: + default: false + description: Gives Crossbows abilities & skills a 33.3% better chance to activate. mcmmo.perks.lucky.excavation: default: false description: Gives Excavation abilities & skills a 33.3% better chance to activate. @@ -1479,6 +1529,9 @@ permissions: mcmmo.perks.lucky.taming: default: false description: Gives Taming abilities & skills a 33.3% better chance to activate. + mcmmo.perks.lucky.tridents: + default: false + description: Gives Tridents abilities & skills a 33.3% better chance to activate. mcmmo.perks.lucky.unarmed: default: false description: Gives Unarmed abilities & skills a 33.3% better chance to activate. @@ -1520,6 +1573,7 @@ permissions: mcmmo.perks.xp.150percentboost.alchemy: true mcmmo.perks.xp.150percentboost.archery: true mcmmo.perks.xp.150percentboost.axes: true + mcmmo.perks.xp.150percentboost.crossbows: true mcmmo.perks.xp.150percentboost.excavation: true mcmmo.perks.xp.150percentboost.fishing: true mcmmo.perks.xp.150percentboost.herbalism: true @@ -1528,6 +1582,7 @@ permissions: mcmmo.perks.xp.150percentboost.smelting: true mcmmo.perks.xp.150percentboost.swords: true mcmmo.perks.xp.150percentboost.taming: true + mcmmo.perks.xp.150percentboost.tridents: true mcmmo.perks.xp.150percentboost.unarmed: true mcmmo.perks.xp.150percentboost.woodcutting: true mcmmo.perks.xp.150percentboost.acrobatics: @@ -1542,6 +1597,9 @@ permissions: mcmmo.perks.xp.150percentboost.axes: default: false description: Multiplies incoming Axes XP by 2.5 + mcmmo.perks.xp.150percentboost.crossbows: + default: false + description: Multiplies incoming Crossbows XP by 2.5 mcmmo.perks.xp.150percentboost.excavation: default: false description: Multiplies incoming Excavation XP by 2.5 @@ -1566,6 +1624,9 @@ permissions: mcmmo.perks.xp.150percentboost.taming: default: false description: Multiplies incoming Taming XP by 2.5 + mcmmo.perks.xp.150percentboost.tridents: + default: false + description: Multiplies incoming Tridents XP by 2.5 mcmmo.perks.xp.150percentboost.unarmed: default: false description: Multiplies incoming Unarmed XP by 2.5 @@ -1590,6 +1651,7 @@ permissions: mcmmo.perks.xp.50percentboost.alchemy: true mcmmo.perks.xp.50percentboost.archery: true mcmmo.perks.xp.50percentboost.axes: true + mcmmo.perks.xp.50percentboost.crossbows: true mcmmo.perks.xp.50percentboost.excavation: true mcmmo.perks.xp.50percentboost.fishing: true mcmmo.perks.xp.50percentboost.herbalism: true @@ -1598,6 +1660,7 @@ permissions: mcmmo.perks.xp.50percentboost.smelting: true mcmmo.perks.xp.50percentboost.swords: true mcmmo.perks.xp.50percentboost.taming: true + mcmmo.perks.xp.50percentboost.tridents: true mcmmo.perks.xp.50percentboost.unarmed: true mcmmo.perks.xp.50percentboost.woodcutting: true mcmmo.perks.xp.50percentboost.acrobatics: @@ -1612,6 +1675,9 @@ permissions: mcmmo.perks.xp.50percentboost.axes: default: false description: Multiplies incoming Axes XP by 1.5 + mcmmo.perks.xp.50percentboost.crossbows: + default: false + description: Multiplies incoming Crossbows XP by 1.5 mcmmo.perks.xp.50percentboost.excavation: default: false description: Multiplies incoming Excavation XP by 1.5 @@ -1636,6 +1702,9 @@ permissions: mcmmo.perks.xp.50percentboost.taming: default: false description: Multiplies incoming Taming XP by 1.5 + mcmmo.perks.xp.50percentboost.tridents: + default: false + description: Multiplies incoming Tridents XP by 1.5 mcmmo.perks.xp.50percentboost.unarmed: default: false description: Multiplies incoming Unarmed XP by 1.5 @@ -1656,20 +1725,22 @@ permissions: default: false description: Multiplies incoming XP by 1.25 children: - mcmmo.perks.xp.25percentboost.acrobatics: true - mcmmo.perks.xp.25percentboost.alchemy: true - mcmmo.perks.xp.25percentboost.archery: true - mcmmo.perks.xp.25percentboost.axes: true - mcmmo.perks.xp.25percentboost.excavation: true - mcmmo.perks.xp.25percentboost.fishing: true - mcmmo.perks.xp.25percentboost.herbalism: true - mcmmo.perks.xp.25percentboost.mining: true - mcmmo.perks.xp.25percentboost.repair: true - mcmmo.perks.xp.25percentboost.smelting: true - mcmmo.perks.xp.25percentboost.swords: true - mcmmo.perks.xp.25percentboost.taming: true - mcmmo.perks.xp.25percentboost.unarmed: true - mcmmo.perks.xp.25percentboost.woodcutting: true + mcmmo.perks.xp.25percentboost.acrobatics: true + mcmmo.perks.xp.25percentboost.alchemy: true + mcmmo.perks.xp.25percentboost.archery: true + mcmmo.perks.xp.25percentboost.axes: true + mcmmo.perks.xp.25percentboost.crossbows: true + mcmmo.perks.xp.25percentboost.excavation: true + mcmmo.perks.xp.25percentboost.fishing: true + mcmmo.perks.xp.25percentboost.herbalism: true + mcmmo.perks.xp.25percentboost.mining: true + mcmmo.perks.xp.25percentboost.repair: true + mcmmo.perks.xp.25percentboost.smelting: true + mcmmo.perks.xp.25percentboost.swords: true + mcmmo.perks.xp.25percentboost.taming: true + mcmmo.perks.xp.25percentboost.tridents: true + mcmmo.perks.xp.25percentboost.unarmed: true + mcmmo.perks.xp.25percentboost.woodcutting: true mcmmo.perks.xp.25percentboost.acrobatics: default: false description: Multiplies incoming Acrobatics XP by 1.25 @@ -1682,6 +1753,9 @@ permissions: mcmmo.perks.xp.25percentboost.axes: default: false description: Multiplies incoming Axes XP by 1.25 + mcmmo.perks.xp.25percentboost.crossbows: + default: false + description: Multiplies incoming Crossbows XP by 1.25 mcmmo.perks.xp.25percentboost.excavation: default: false description: Multiplies incoming Excavation XP by 1.25 @@ -1706,6 +1780,9 @@ permissions: mcmmo.perks.xp.25percentboost.taming: default: false description: Multiplies incoming Taming XP by 1.25 + mcmmo.perks.xp.25percentboost.tridents: + default: false + description: Multiplies incoming Tridents XP by 1.25 mcmmo.perks.xp.25percentboost.unarmed: default: false description: Multiplies incoming Unarmed XP by 1.5 @@ -1730,6 +1807,7 @@ permissions: mcmmo.perks.xp.10percentboost.alchemy: true mcmmo.perks.xp.10percentboost.archery: true mcmmo.perks.xp.10percentboost.axes: true + mcmmo.perks.xp.10percentboost.crossbows: true mcmmo.perks.xp.10percentboost.excavation: true mcmmo.perks.xp.10percentboost.fishing: true mcmmo.perks.xp.10percentboost.herbalism: true @@ -1738,6 +1816,7 @@ permissions: mcmmo.perks.xp.10percentboost.smelting: true mcmmo.perks.xp.10percentboost.swords: true mcmmo.perks.xp.10percentboost.taming: true + mcmmo.perks.xp.10percentboost.tridents: true mcmmo.perks.xp.10percentboost.unarmed: true mcmmo.perks.xp.10percentboost.woodcutting: true mcmmo.perks.xp.10percentboost.acrobatics: @@ -1752,6 +1831,9 @@ permissions: mcmmo.perks.xp.10percentboost.axes: default: false description: Multiplies incoming Axes XP by 1.1 + mcmmo.perks.xp.10percentboost.crossbows: + default: false + description: Multiplies incoming Crossbows XP by 1.1 mcmmo.perks.xp.10percentboost.excavation: default: false description: Multiplies incoming Excavation XP by 1.1 @@ -1776,6 +1858,9 @@ permissions: mcmmo.perks.xp.10percentboost.taming: default: false description: Multiplies incoming Taming XP by 1.1 + mcmmo.perks.xp.10percentboost.tridents: + default: false + description: Multiplies incoming Tridents XP by 1.1 mcmmo.perks.xp.10percentboost.unarmed: default: false description: Multiplies incoming Unarmed XP by 1.1 @@ -1800,6 +1885,7 @@ permissions: mcmmo.perks.xp.customboost.alchemy: true mcmmo.perks.xp.customboost.archery: true mcmmo.perks.xp.customboost.axes: true + mcmmo.perks.xp.customboost.crossbows: true mcmmo.perks.xp.customboost.excavation: true mcmmo.perks.xp.customboost.fishing: true mcmmo.perks.xp.customboost.herbalism: true @@ -1808,6 +1894,7 @@ permissions: mcmmo.perks.xp.customboost.smelting: true mcmmo.perks.xp.customboost.swords: true mcmmo.perks.xp.customboost.taming: true + mcmmo.perks.xp.customboost.tridents: true mcmmo.perks.xp.customboost.unarmed: true mcmmo.perks.xp.customboost.woodcutting: true mcmmo.perks.xp.customboost.acrobatics: @@ -1822,6 +1909,9 @@ permissions: mcmmo.perks.xp.customboost.axes: default: false description: Multiplies incoming Axes XP by the boost amount defined in the experience config + mcmmo.perks.xp.customboost.crossbows: + default: false + description: Multiplies incoming Crossbows XP by the boost amount defined in the experience config mcmmo.perks.xp.customboost.excavation: default: false description: Multiplies incoming Excavation XP by the boost amount defined in the experience config @@ -1846,6 +1936,9 @@ permissions: mcmmo.perks.xp.customboost.taming: default: false description: Multiplies incoming Taming XP by the boost amount defined in the experience config + mcmmo.perks.xp.customboost.tridents: + default: false + description: Multiplies incoming Tridents XP by the boost amount defined in the experience config mcmmo.perks.xp.customboost.unarmed: default: false description: Multiplies incoming Unarmed XP by the boost amount defined in the experience config @@ -1870,6 +1963,7 @@ permissions: mcmmo.perks.xp.double.alchemy: true mcmmo.perks.xp.double.archery: true mcmmo.perks.xp.double.axes: true + mcmmo.perks.xp.double.crossbows: true mcmmo.perks.xp.double.excavation: true mcmmo.perks.xp.double.fishing: true mcmmo.perks.xp.double.herbalism: true @@ -1878,6 +1972,7 @@ permissions: mcmmo.perks.xp.double.smelting: true mcmmo.perks.xp.double.swords: true mcmmo.perks.xp.double.taming: true + mcmmo.perks.xp.double.tridents: true mcmmo.perks.xp.double.unarmed: true mcmmo.perks.xp.double.woodcutting: true mcmmo.perks.xp.double.acrobatics: @@ -1892,6 +1987,9 @@ permissions: mcmmo.perks.xp.double.axes: default: false description: Doubles incoming Axes XP + mcmmo.perks.xp.double.crossbows: + default: false + description: Doubles incoming Crossbows XP mcmmo.perks.xp.double.excavation: default: false description: Doubles incoming Excavation XP @@ -1916,6 +2014,9 @@ permissions: mcmmo.perks.xp.double.taming: default: false description: Doubles incoming Taming XP + mcmmo.perks.xp.double.tridents: + default: false + description: Doubles incoming Tridents XP mcmmo.perks.xp.double.unarmed: default: false description: Doubles incoming Unarmed XP @@ -1940,6 +2041,7 @@ permissions: mcmmo.perks.xp.quadruple.alchemy: true mcmmo.perks.xp.quadruple.archery: true mcmmo.perks.xp.quadruple.axes: true + mcmmo.perks.xp.quadruple.crossbows: true mcmmo.perks.xp.quadruple.excavation: true mcmmo.perks.xp.quadruple.fishing: true mcmmo.perks.xp.quadruple.herbalism: true @@ -1948,6 +2050,7 @@ permissions: mcmmo.perks.xp.quadruple.smelting: true mcmmo.perks.xp.quadruple.swords: true mcmmo.perks.xp.quadruple.taming: true + mcmmo.perks.xp.quadruple.tridents: true mcmmo.perks.xp.quadruple.unarmed: true mcmmo.perks.xp.quadruple.woodcutting: true mcmmo.perks.xp.quadruple.acrobatics: @@ -1962,6 +2065,9 @@ permissions: mcmmo.perks.xp.quadruple.axes: default: false description: Quadruples incoming Axes XP + mcmmo.perks.xp.quadruple.crossbows: + default: false + description: Quadruples incoming Crossbows XP mcmmo.perks.xp.quadruple.excavation: default: false description: Quadruples incoming Excavation XP @@ -1986,6 +2092,9 @@ permissions: mcmmo.perks.xp.quadruple.taming: default: false description: Quadruples incoming Taming XP + mcmmo.perks.xp.quadruple.tridents: + default: false + description: Quadruples incoming Tridents XP mcmmo.perks.xp.quadruple.unarmed: default: false description: Quadruples incoming Unarmed XP @@ -2010,6 +2119,7 @@ permissions: mcmmo.perks.xp.triple.alchemy: true mcmmo.perks.xp.triple.archery: true mcmmo.perks.xp.triple.axes: true + mcmmo.perks.xp.triple.crossbows: true mcmmo.perks.xp.triple.excavation: true mcmmo.perks.xp.triple.fishing: true mcmmo.perks.xp.triple.herbalism: true @@ -2018,6 +2128,7 @@ permissions: mcmmo.perks.xp.triple.smelting: true mcmmo.perks.xp.triple.swords: true mcmmo.perks.xp.triple.taming: true + mcmmo.perks.xp.triple.tridents: true mcmmo.perks.xp.triple.unarmed: true mcmmo.perks.xp.triple.woodcutting: true mcmmo.perks.xp.triple.acrobatics: @@ -2032,6 +2143,9 @@ permissions: mcmmo.perks.xp.triple.axes: default: false description: Triples incoming Axes XP + mcmmo.perks.xp.triple.crossbows: + default: false + description: Triples incoming Crossbows XP mcmmo.perks.xp.triple.excavation: default: false description: Triples incoming Excavation XP @@ -2056,6 +2170,9 @@ permissions: mcmmo.perks.xp.triple.taming: default: false description: Triples incoming Taming XP + mcmmo.perks.xp.triple.tridents: + default: false + description: Triples incoming Tridents XP mcmmo.perks.xp.triple.unarmed: default: false description: Triples incoming Unarmed XP @@ -2091,6 +2208,8 @@ permissions: mcmmo.skills.taming: true mcmmo.skills.unarmed: true mcmmo.skills.woodcutting: true + mcmmo.skills.crossbows: true + mcmmo.skills.tridents: true mcmmo.skills.acrobatics: description: Allows access to the Acrobatics skill children: @@ -2111,6 +2230,11 @@ permissions: children: mcmmo.ability.axes.all: true mcmmo.commands.axes: true + mcmmo.skills.crossbows: + description: Allows access to the Crossbows skill + children: + mcmmo.ability.crossbows.all: true + mcmmo.commands.crossbows: true mcmmo.skills.excavation: description: Allows access to the Excavation skill children: @@ -2156,6 +2280,11 @@ permissions: children: mcmmo.ability.taming.all: true mcmmo.commands.taming: true + mcmmo.skills.tridents: + description: Allows access to the Tridents skill + children: + mcmmo.ability.tridents.all: true + mcmmo.commands.tridents: true mcmmo.skills.unarmed: description: Allows access to the Unarmed skill children: @@ -2169,16 +2298,3 @@ permissions: mcmmo.showversion: default: true description: Show mcMMO version number in /mcmmo and motd - mcmmo.tools.*: - default: false - description: Implies all mcmmo.tools permissions. - children: - mcmmo.tools.all: true - mcmmo.tools.all: - default: false - description: Implies all mcmmo.tools permissions. - children: - mcmmo.tools.updatecheck: true - mcmmo.tools.updatecheck: - default: false - description: Notifies admins if there is a new version of mcMMO available diff --git a/src/main/resources/skillranks.yml b/src/main/resources/skillranks.yml index 1a305921b..590ddde11 100644 --- a/src/main/resources/skillranks.yml +++ b/src/main/resources/skillranks.yml @@ -201,6 +201,129 @@ Axes: Rank_2: 100 Rank_3: 150 Rank_4: 200 +Crossbows: + TrickShot: + Standard: + Rank_1: 5 + Rank_2: 20 + Rank_3: 40 + RetroMode: + Rank_1: 50 + Rank_2: 200 + Rank_3: 400 + PoweredShot: + Standard: + Rank_1: 1 + Rank_2: 10 + Rank_3: 15 + Rank_4: 20 + Rank_5: 25 + Rank_6: 30 + Rank_7: 35 + Rank_8: 40 + Rank_9: 45 + Rank_10: 50 + Rank_11: 55 + Rank_12: 60 + Rank_13: 65 + Rank_14: 70 + Rank_15: 75 + Rank_16: 80 + Rank_17: 85 + Rank_18: 90 + Rank_19: 95 + Rank_20: 100 + RetroMode: + Rank_1: 1 + Rank_2: 100 + Rank_3: 150 + Rank_4: 200 + Rank_5: 250 + Rank_6: 300 + Rank_7: 350 + Rank_8: 400 + Rank_9: 450 + Rank_10: 500 + Rank_11: 550 + Rank_12: 600 + Rank_13: 650 + Rank_14: 700 + Rank_15: 750 + Rank_16: 800 + Rank_17: 850 + Rank_18: 900 + Rank_19: 950 + Rank_20: 1000 + CrossbowsLimitBreak: + Standard: + Rank_1: 10 + Rank_2: 20 + Rank_3: 30 + Rank_4: 40 + Rank_5: 50 + Rank_6: 60 + Rank_7: 70 + Rank_8: 80 + Rank_9: 90 + Rank_10: 100 + RetroMode: + Rank_1: 100 + Rank_2: 200 + Rank_3: 300 + Rank_4: 400 + Rank_5: 500 + Rank_6: 600 + Rank_7: 700 + Rank_8: 800 + Rank_9: 900 + Rank_10: 1000 +Tridents: + TridentsLimitBreak: + Standard: + Rank_1: 10 + Rank_2: 20 + Rank_3: 30 + Rank_4: 40 + Rank_5: 50 + Rank_6: 60 + Rank_7: 70 + Rank_8: 80 + Rank_9: 90 + Rank_10: 100 + RetroMode: + Rank_1: 100 + Rank_2: 200 + Rank_3: 300 + Rank_4: 400 + Rank_5: 500 + Rank_6: 600 + Rank_7: 700 + Rank_8: 800 + Rank_9: 900 + Rank_10: 1000 + Impale: + Standard: + Rank_1: 5 + Rank_2: 15 + Rank_3: 25 + Rank_4: 35 + Rank_5: 45 + Rank_6: 55 + Rank_7: 65 + Rank_8: 75 + Rank_9: 85 + Rank_10: 100 + RetroMode: + Rank_1: 50 + Rank_2: 150 + Rank_3: 250 + Rank_4: 350 + Rank_5: 450 + Rank_6: 550 + Rank_7: 650 + Rank_8: 750 + Rank_9: 850 + Rank_10: 1000 Taming: BeastLore: Standard: @@ -321,6 +444,11 @@ Salvage: Rank_7: 850 Rank_8: 1000 Mining: + MotherLode: + Standard: + Rank_1: 100 + RetroMode: + Rank_1: 1000 DoubleDrops: Standard: Rank_1: 1 @@ -368,6 +496,11 @@ Herbalism: Rank_1: 1 RetroMode: Rank_1: 1 + VerdantBounty: + Standard: + Rank_1: 100 + RetroMode: + Rank_1: 1000 GreenTerra: Standard: Rank_1: 5 @@ -398,6 +531,11 @@ Herbalism: Rank_4: 800 Rank_5: 1000 Fishing: + Mastery: + Standard: + Rank_1: 100 + RetroMode: + Rank_1: 1000 MagicHunter: Standard: Rank_1: 20 @@ -637,6 +775,11 @@ Woodcutting: Rank_1: 1 RetroMode: Rank_1: 1 + CleanCuts: + Standard: + Rank_1: 100 + RetroMode: + Rank_1: 1000 KnockOnWood: Standard: Rank_1: 30 diff --git a/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java b/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java new file mode 100644 index 000000000..d995ed569 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java @@ -0,0 +1,216 @@ +package com.gmail.nossr50; + +import com.gmail.nossr50.api.exceptions.InvalidSkillException; +import com.gmail.nossr50.config.AdvancedConfig; +import com.gmail.nossr50.config.ChatConfig; +import com.gmail.nossr50.config.GeneralConfig; +import com.gmail.nossr50.config.RankConfig; +import com.gmail.nossr50.config.experience.ExperienceConfig; +import com.gmail.nossr50.config.party.PartyConfig; +import com.gmail.nossr50.datatypes.player.McMMOPlayer; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.util.*; +import com.gmail.nossr50.util.blockmeta.ChunkManager; +import com.gmail.nossr50.util.player.UserManager; +import com.gmail.nossr50.util.skills.RankUtils; +import com.gmail.nossr50.util.skills.SkillTools; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.PluginManager; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.UUID; +import java.util.logging.Logger; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +public abstract class MMOTestEnvironment { + protected MockedStatic mockedMcMMO; + protected MockedStatic mockedChatConfig; + protected MockedStatic experienceConfig; + protected MockedStatic mockedPermissions; + protected MockedStatic mockedRankUtils; + protected MockedStatic mockedUserManager; + protected MockedStatic mockedMisc; + protected MockedStatic mockedSkillTools; + protected MockedStatic mockedEventUtils; + protected TransientEntityTracker transientEntityTracker; + protected AdvancedConfig advancedConfig; + protected PartyConfig partyConfig; + protected GeneralConfig generalConfig; + protected RankConfig rankConfig; + protected SkillTools skillTools; + protected Server server; + protected PluginManager pluginManager; + protected World world; + + /* Mocks */ + protected Player player; + + protected UUID playerUUID = UUID.randomUUID(); + protected ItemStack itemInMainHand; + + protected PlayerInventory playerInventory; + protected PlayerProfile playerProfile; + protected McMMOPlayer mmoPlayer; + protected String playerName = "testPlayer"; + + protected ChunkManager chunkManager; + protected MaterialMapStore materialMapStore; + + protected void mockBaseEnvironment(Logger logger) throws InvalidSkillException { + mockedMcMMO = Mockito.mockStatic(mcMMO.class); + mcMMO.p = Mockito.mock(mcMMO.class); + when(mcMMO.p.getLogger()).thenReturn(logger); + + // place store + chunkManager = Mockito.mock(ChunkManager.class); + when(mcMMO.getPlaceStore()).thenReturn(chunkManager); + + // shut off mod manager for woodcutting + when(mcMMO.getModManager()).thenReturn(Mockito.mock(ModManager.class)); + when(mcMMO.getModManager().isCustomLog(any())).thenReturn(false); + + // chat config + mockedChatConfig = Mockito.mockStatic(ChatConfig.class); + when(ChatConfig.getInstance()).thenReturn(Mockito.mock(ChatConfig.class)); + + // general config + mockGeneralConfig(); + + // party config + mockPartyConfig(); + + // rank config + mockRankConfig(); + + // wire advanced config + mockAdvancedConfig(); + + // wire experience config + mockExperienceConfig(); + + // wire skill tools + this.skillTools = new SkillTools(mcMMO.p); + when(mcMMO.p.getSkillTools()).thenReturn(skillTools); + + this.transientEntityTracker = new TransientEntityTracker(); + when(mcMMO.getTransientEntityTracker()).thenReturn(transientEntityTracker); + + mockPermissions(); + + mockedRankUtils = Mockito.mockStatic(RankUtils.class); + + // wire server + this.server = Mockito.mock(Server.class); + when(mcMMO.p.getServer()).thenReturn(server); + + // wire plugin manager + this.pluginManager = Mockito.mock(PluginManager.class); + when(server.getPluginManager()).thenReturn(pluginManager); + + // wire world + this.world = Mockito.mock(World.class); + + // wire Misc + this.mockedMisc = Mockito.mockStatic(Misc.class); + when(Misc.getBlockCenter(any())).thenReturn(new Location(world, 0, 0, 0)); + + // setup player and player related mocks after everything else + this.player = Mockito.mock(Player.class); + when(player.getUniqueId()).thenReturn(playerUUID); + + // wire inventory + this.playerInventory = Mockito.mock(PlayerInventory.class); + when(player.getInventory()).thenReturn(playerInventory); + + // PlayerProfile and McMMOPlayer are partially mocked + playerProfile = new PlayerProfile("testPlayer", player.getUniqueId(), 0); + mmoPlayer = Mockito.spy(new McMMOPlayer(player, playerProfile)); + + // wire user manager + this.mockedUserManager = Mockito.mockStatic(UserManager.class); + when(UserManager.getPlayer(player)).thenReturn(mmoPlayer); + + this.materialMapStore = new MaterialMapStore(); + when(mcMMO.getMaterialMapStore()).thenReturn(materialMapStore); + } + + private void mockPermissions() { + mockedPermissions = Mockito.mockStatic(Permissions.class); + when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true); + when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true); + when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true); + when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true); + when(Permissions.lucky(player, PrimarySkillType.WOODCUTTING)).thenReturn(false); // player is not lucky + } + + private void mockRankConfig() { + rankConfig = Mockito.mock(RankConfig.class); + } + + private void mockAdvancedConfig() { + this.advancedConfig = Mockito.mock(AdvancedConfig.class); + when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig); + } + + private void mockGeneralConfig() { + generalConfig = Mockito.mock(GeneralConfig.class); + when(generalConfig.getTreeFellerThreshold()).thenReturn(100); + when(generalConfig.getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, Material.OAK_LOG)).thenReturn(true); + when(generalConfig.getLocale()).thenReturn("en_US"); + when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig); + } + + private void mockPartyConfig() { + partyConfig = Mockito.mock(PartyConfig.class); + when(partyConfig.isPartyEnabled()).thenReturn(false); + when(mcMMO.p.getPartyConfig()).thenReturn(partyConfig); + } + + private void mockExperienceConfig() { + experienceConfig = Mockito.mockStatic(ExperienceConfig.class); + + when(ExperienceConfig.getInstance()).thenReturn(Mockito.mock(ExperienceConfig.class)); + + // Combat + when(ExperienceConfig.getInstance().getCombatXP("Cow")).thenReturn(1D); + } + + protected void cleanupBaseEnvironment() { + // Clean up resources here if needed. + if (mockedMcMMO != null) { + mockedMcMMO.close(); + } + if (experienceConfig != null) { + experienceConfig.close(); + } + if (mockedChatConfig != null) { + mockedChatConfig.close(); + } + if (mockedPermissions != null) { + mockedPermissions.close(); + } + if (mockedRankUtils != null) { + mockedRankUtils.close(); + } + if (mockedUserManager != null) { + mockedUserManager.close(); + } + if (mockedMisc != null) { + mockedMisc.close(); + } + if (mockedEventUtils != null) { + mockedEventUtils.close(); + } + } +} diff --git a/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java b/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java index f626002c1..23463cbdb 100644 --- a/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java +++ b/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java @@ -31,7 +31,6 @@ import java.util.logging.Logger; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; -//This class uses JUnit5/Jupiter class FlatFileDatabaseManagerTest { public static final @NotNull String TEST_FILE_NAME = "test.mcmmo.users"; @@ -39,7 +38,7 @@ class FlatFileDatabaseManagerTest { public static final @NotNull String BAD_DATA_FILE_LINE_TWENTY_THREE = "nossr51:baddata:::baddata:baddata:640:baddata:1000:1000:1000:baddata:baddata:baddata:baddata:16:0:500:20273:0:0:0:0::1000:0:0:baddata:1593543012:0:0:0:0::1000:0:0:baddata:IGNORED:1000:0:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1:0:"; public static final @NotNull String DB_BADDATA = "baddatadb.users"; public static final @NotNull String DB_HEALTHY = "healthydb.users"; - public static final @NotNull String HEALTHY_DB_LINE_1 = "nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:"; + public static final @NotNull String HEALTHY_DB_LINE_1 = "nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:140:14:150:15:1111:2222:3333"; public static final @NotNull String HEALTHY_DB_LINE_ONE_UUID_STR = "588fe472-1c82-4c4e-9aa1-7eefccb277e3"; public static final String DB_MISSING_LAST_LOGIN = "missinglastlogin.users"; public static final String LINE_TWO_FROM_MISSING_DB = "nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:0:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:"; @@ -52,16 +51,19 @@ class FlatFileDatabaseManagerTest { int expectedLvlMining = 1, expectedLvlWoodcutting = 2, expectedLvlRepair = 3, expectedLvlUnarmed = 4, expectedLvlHerbalism = 5, expectedLvlExcavation = 6, expectedLvlArchery = 7, expectedLvlSwords = 8, expectedLvlAxes = 9, expectedLvlAcrobatics = 10, - expectedLvlTaming = 11, expectedLvlFishing = 12, expectedLvlAlchemy = 13; + expectedLvlTaming = 11, expectedLvlFishing = 12, expectedLvlAlchemy = 13, expectedLvlCrossbows = 14, + expectedLvlTridents = 15; float expectedExpMining = 10, expectedExpWoodcutting = 20, expectedExpRepair = 30, expectedExpUnarmed = 40, expectedExpHerbalism = 50, expectedExpExcavation = 60, expectedExpArchery = 70, expectedExpSwords = 80, expectedExpAxes = 90, expectedExpAcrobatics = 100, - expectedExpTaming = 110, expectedExpFishing = 120, expectedExpAlchemy = 130; + expectedExpTaming = 110, expectedExpFishing = 120, expectedExpAlchemy = 130, expectedExpCrossbows = 140, + expectedExpTridents = 150; long expectedBerserkCd = 111, expectedGigaDrillBreakerCd = 222, expectedTreeFellerCd = 333, expectedGreenTerraCd = 444, expectedSerratedStrikesCd = 555, expectedSkullSplitterCd = 666, - expectedSuperBreakerCd = 777, expectedBlastMiningCd = 888, expectedChimaeraWingCd = 999; + expectedSuperBreakerCd = 777, expectedBlastMiningCd = 888, expectedChimaeraWingCd = 999, + expectedSuperShotgunCd = 1111, expectedTridentSuperCd = 2222, expectedExplosiveShotCd = 3333; int expectedScoreboardTips = 1111; Long expectedLastLogin = 2020L; @@ -226,7 +228,6 @@ class FlatFileDatabaseManagerTest { logger.info("File Path: "+healthyDB.getAbsolutePath()); assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0)); assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR); - UUID healthDBEntryOneUUID = UUID.fromString(HEALTHY_DB_LINE_ONE_UUID_STR); db = new FlatFileDatabaseManager(healthyDB, logger, PURGE_TIME, 0, true); List flagsFound = db.checkFileHealthAndStructure(); @@ -451,14 +452,13 @@ class FlatFileDatabaseManagerTest { if(SkillTools.isChildSkill(primarySkillType)) continue; -// logger.info("Checking expected values for: "+primarySkillType); -// logger.info("Profile Level Value: "+profile.getSkillLevel(primarySkillType)); -// logger.info("Expected Lvl Value: "+getExpectedLevelHealthyDBEntryOne(primarySkillType)); -// logger.info("Profile Exp Value: "+profile.getSkillXpLevelRaw(primarySkillType)); -// logger.info("Expected Exp Value: "+getExpectedExperienceHealthyDBEntryOne(primarySkillType)); + int expectedLevelHealthyDBEntryOne = getExpectedLevelHealthyDBEntryOne(primarySkillType); + int skillLevel = profile.getSkillLevel(primarySkillType); + assertEquals(expectedLevelHealthyDBEntryOne, skillLevel); - assertEquals(getExpectedLevelHealthyDBEntryOne(primarySkillType), profile.getSkillLevel(primarySkillType)); - assertEquals(getExpectedExperienceHealthyDBEntryOne(primarySkillType), profile.getSkillXpLevelRaw(primarySkillType), 0); + float expectedExperienceHealthyDBEntryOne = getExpectedExperienceHealthyDBEntryOne(primarySkillType); + float skillXpLevelRaw = profile.getSkillXpLevelRaw(primarySkillType); + assertEquals(expectedExperienceHealthyDBEntryOne, skillXpLevelRaw, 0); } //Check the other things @@ -472,29 +472,24 @@ class FlatFileDatabaseManagerTest { } private long getExpectedSuperAbilityDATS(@NotNull SuperAbilityType superAbilityType) { - switch(superAbilityType) { - case BERSERK: - return expectedBerserkCd; - case SUPER_BREAKER: - return expectedSuperBreakerCd; - case GIGA_DRILL_BREAKER: - return expectedGigaDrillBreakerCd; - case GREEN_TERRA: - return expectedGreenTerraCd; - case SKULL_SPLITTER: - return expectedSkullSplitterCd; - case TREE_FELLER: - return expectedTreeFellerCd; - case SERRATED_STRIKES: - return expectedSerratedStrikesCd; - case BLAST_MINING: - return expectedBlastMiningCd; - } + return switch (superAbilityType) { + case BERSERK -> expectedBerserkCd; + case SUPER_BREAKER -> expectedSuperBreakerCd; + case GIGA_DRILL_BREAKER -> expectedGigaDrillBreakerCd; + case GREEN_TERRA -> expectedGreenTerraCd; + case SKULL_SPLITTER -> expectedSkullSplitterCd; + case SUPER_SHOTGUN -> expectedSuperShotgunCd; + case TREE_FELLER -> expectedTreeFellerCd; + case SERRATED_STRIKES -> expectedSerratedStrikesCd; + case BLAST_MINING -> expectedBlastMiningCd; + case TRIDENTS_SUPER_ABILITY -> expectedTridentSuperCd; + case EXPLOSIVE_SHOT -> expectedExplosiveShotCd; + default -> throw new RuntimeException("Values not defined for super ability please add " + + "values for " + superAbilityType.toString() + " to the test"); + }; - return -1; } - //TODO: Why is this stuff a float? private float getExpectedExperienceHealthyDBEntryOne(@NotNull PrimarySkillType primarySkillType) { switch(primarySkillType) { case ACROBATICS: @@ -505,6 +500,8 @@ class FlatFileDatabaseManagerTest { return expectedExpArchery; case AXES: return expectedExpAxes; + case CROSSBOWS: + return expectedExpCrossbows; case EXCAVATION: return expectedExpExcavation; case FISHING: @@ -522,13 +519,15 @@ class FlatFileDatabaseManagerTest { return expectedExpSwords; case TAMING: return expectedExpTaming; + case TRIDENTS: + return expectedExpTridents; case UNARMED: return expectedExpUnarmed; case WOODCUTTING: return expectedExpWoodcutting; } - return -1; + throw new RuntimeException("Values for skill not defined, please add values for " + primarySkillType.toString() + " to the test"); } private int getExpectedLevelHealthyDBEntryOne(@NotNull PrimarySkillType primarySkillType) { @@ -541,6 +540,8 @@ class FlatFileDatabaseManagerTest { return expectedLvlArchery; case AXES: return expectedLvlAxes; + case CROSSBOWS: + return expectedLvlCrossbows; case EXCAVATION: return expectedLvlExcavation; case FISHING: @@ -558,13 +559,15 @@ class FlatFileDatabaseManagerTest { return expectedLvlSwords; case TAMING: return expectedLvlTaming; + case TRIDENTS: + return expectedLvlTridents; case UNARMED: return expectedLvlUnarmed; case WOODCUTTING: return expectedLvlWoodcutting; } - return -1; + throw new RuntimeException("Values for skill not defined, please add values for " + primarySkillType.toString() + " to the test"); } @Test diff --git a/src/test/java/com/gmail/nossr50/database/SQLDatabaseManagerTest.java b/src/test/java/com/gmail/nossr50/database/SQLDatabaseManagerTest.java new file mode 100644 index 000000000..9a183f2e4 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/database/SQLDatabaseManagerTest.java @@ -0,0 +1,245 @@ +package com.gmail.nossr50.database; + +import com.gmail.nossr50.config.AdvancedConfig; +import com.gmail.nossr50.config.GeneralConfig; +import com.gmail.nossr50.datatypes.MobHealthbarType; +import com.gmail.nossr50.datatypes.player.PlayerProfile; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.mcMMO; +import com.gmail.nossr50.util.compat.CompatibilityManager; +import com.gmail.nossr50.util.platform.MinecraftGameVersion; +import com.gmail.nossr50.util.skills.SkillTools; +import com.gmail.nossr50.util.upgrade.UpgradeManager; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.*; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +class SQLDatabaseManagerTest { + private final static @NotNull Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); + static MockedStatic mockedMcMMO; + SQLDatabaseManager sqlDatabaseManager; + static GeneralConfig generalConfig; + static AdvancedConfig advancedConfig; + static UpgradeManager upgradeManager; + static CompatibilityManager compatibilityManager; + static SkillTools skillTools; + + @BeforeAll + static void setUpAll() { + // stub mcMMO.p + mockedMcMMO = Mockito.mockStatic(mcMMO.class); + mcMMO.p = Mockito.mock(mcMMO.class); + when(mcMMO.p.getLogger()).thenReturn(logger); + + // general config mock + mockGeneralConfig(); + + // advanced config mock + advancedConfig = Mockito.mock(AdvancedConfig.class); + when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig); + + // starting level + when(mcMMO.p.getAdvancedConfig().getStartingLevel()).thenReturn(0); + + // wire skill tools + skillTools = new SkillTools(mcMMO.p); + when(mcMMO.p.getSkillTools()).thenReturn(skillTools); + + // compatibility manager mock + compatibilityManager = Mockito.mock(CompatibilityManager.class); + when(mcMMO.getCompatibilityManager()).thenReturn(compatibilityManager); + when(compatibilityManager.getMinecraftGameVersion()).thenReturn(new MinecraftGameVersion(1, 20, 4)); + + // upgrade manager mock + upgradeManager = Mockito.mock(UpgradeManager.class); + when(mcMMO.getUpgradeManager()).thenReturn(upgradeManager); + + // don't trigger upgrades + when(mcMMO.getUpgradeManager().shouldUpgrade(any())).thenReturn(false); + } + + private static void mockGeneralConfig() { + generalConfig = Mockito.mock(GeneralConfig.class); + when(generalConfig.getLocale()).thenReturn("en_US"); + when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig); + + // max pool size + when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.MISC)) + .thenReturn(10); + when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.LOAD)) + .thenReturn(20); + when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.SAVE)) + .thenReturn(20); + + // max connections + when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.MISC)) + .thenReturn(30); + when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.LOAD)) + .thenReturn(30); + when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.SAVE)) + .thenReturn(30); + + // table prefix + when(mcMMO.p.getGeneralConfig().getMySQLTablePrefix()).thenReturn("mcmmo_"); + + // public key retrieval + when(mcMMO.p.getGeneralConfig().getMySQLPublicKeyRetrieval()).thenReturn(true); + + // debug + when(mcMMO.p.getGeneralConfig().getMySQLDebug()).thenReturn(true); + + // use mysql + when(mcMMO.p.getGeneralConfig().getUseMySQL()).thenReturn(true); + + // use ssl + when(mcMMO.p.getGeneralConfig().getMySQLSSL()).thenReturn(true); + + // username + when(mcMMO.p.getGeneralConfig().getMySQLUserName()).thenReturn("sa"); + + // password + when(mcMMO.p.getGeneralConfig().getMySQLUserPassword()).thenReturn(""); + + // host + when(mcMMO.p.getGeneralConfig().getMySQLServerName()).thenReturn("localhost"); + + // unused mob health bar thingy + when(mcMMO.p.getGeneralConfig().getMobHealthbarDefault()).thenReturn(MobHealthbarType.HEARTS); + } + + @BeforeEach + void setUp() { + assertNull(sqlDatabaseManager); + sqlDatabaseManager = new SQLDatabaseManager(logger, "org.h2.Driver", true); + } + + @AfterEach + void tearDown() { + sqlDatabaseManager = null; + } + + @AfterAll + static void tearDownAll() { + mockedMcMMO.close(); + } + + @Test + void testGetConnectionMisc() throws Exception { + assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.MISC)); + } + + @Test + void testGetConnectionLoad() throws Exception { + assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.LOAD)); + } + + @Test + void testGetConnectionSave() throws Exception { + assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.SAVE)); + } + + @Test + void testNewUser() { + Player player = Mockito.mock(Player.class); + when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); + when(player.getName()).thenReturn("nossr50"); + sqlDatabaseManager.newUser(player); + } + + @Test + void testNewUserGetSkillLevel() { + Player player = Mockito.mock(Player.class); + when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); + when(player.getName()).thenReturn("nossr50"); + PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); + + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + assertEquals(0, playerProfile.getSkillLevel(primarySkillType)); + } + } + + @Test + void testNewUserGetSkillXpLevel() { + Player player = Mockito.mock(Player.class); + when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); + when(player.getName()).thenReturn("nossr50"); + PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); + + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType)); + } + } + + @Test + void testSaveSkillLevelValues() { + Player player = Mockito.mock(Player.class); + when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); + when(player.getName()).thenReturn("nossr50"); + PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); + + // Validate values are starting from zero + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType)); + } + + // Change values + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + playerProfile.modifySkill(primarySkillType, 1 + primarySkillType.ordinal()); + } + + boolean saveSuccess = sqlDatabaseManager.saveUser(playerProfile); + assertTrue(saveSuccess); + + PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName()); + + // Check that values got saved + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) { + // Child skills are not saved, but calculated + continue; + } + + assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillLevel(primarySkillType)); + } + } + + @Test + void testSaveSkillXpValues() { + Player player = Mockito.mock(Player.class); + when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID()); + when(player.getName()).thenReturn("nossr50"); + PlayerProfile playerProfile = sqlDatabaseManager.newUser(player); + + // Validate values are starting from zero + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType)); + } + + // Change values + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + playerProfile.setSkillXpLevel(primarySkillType, 1 + primarySkillType.ordinal()); + } + + sqlDatabaseManager.saveUser(playerProfile); + + PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName()); + + // Check that values got saved + for (PrimarySkillType primarySkillType : PrimarySkillType.values()) { + if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) { + // Child skills are not saved, but calculated + continue; + } + + assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillXpLevel(primarySkillType)); + } + } +} diff --git a/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java b/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java index bd4e2d629..0d8f01ccb 100644 --- a/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java +++ b/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java @@ -1,45 +1,46 @@ package com.gmail.nossr50.party; +import com.gmail.nossr50.MMOTestEnvironment; import com.gmail.nossr50.datatypes.player.McMMOPlayer; -import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; -import org.bukkit.Server; import org.bukkit.entity.Player; -import org.bukkit.plugin.PluginManager; -import org.junit.jupiter.api.BeforeAll; +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.UUID; +import java.util.logging.Logger; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; -class PartyManagerTest { +class PartyManagerTest extends MMOTestEnvironment { + private static final Logger logger = Logger.getLogger(PartyManagerTest.class.getName()); - static mcMMO mockMcMMO; + @BeforeEach + public void setUp() { + mockBaseEnvironment(logger); - @BeforeAll - public static void setup() { - // create a static stub for LocaleLoader.class - mockStatic(LocaleLoader.class); - when(LocaleLoader.getString(anyString())).thenReturn(""); + // currently unnecessary, but may be needed for future tests + Mockito.when(partyConfig.isPartyEnabled()).thenReturn(true); + } - mockMcMMO = mock(mcMMO.class); - final Server mockServer = mock(Server.class); - when(mockMcMMO.getServer()).thenReturn(mockServer); - when(mockServer.getPluginManager()).thenReturn(mock(PluginManager.class)); + @AfterEach + public void tearDown() { + cleanupBaseEnvironment(); - // TODO: Add cleanup for static mock + // disable parties in config for other tests + Mockito.when(partyConfig.isPartyEnabled()).thenReturn(false); } @Test public void createPartyWithoutPasswordShouldSucceed() { // Given - PartyManager partyManager = new PartyManager(mockMcMMO); + PartyManager partyManager = new PartyManager(mcMMO.p); String partyName = "TestParty"; - // TODO: Update this with utils from the other dev branches in the future Player player = mock(Player.class); McMMOPlayer mmoPlayer = mock(McMMOPlayer.class); when(mmoPlayer.getPlayer()).thenReturn(player); @@ -52,11 +53,10 @@ class PartyManagerTest { @Test public void createPartyWithPasswordShouldSucceed() { // Given - PartyManager partyManager = new PartyManager(mockMcMMO); + PartyManager partyManager = new PartyManager(mcMMO.p); String partyName = "TestParty"; String partyPassword = "somePassword"; - // TODO: Update this with utils from the other dev branches in the future Player player = mock(Player.class); McMMOPlayer mmoPlayer = mock(McMMOPlayer.class); when(mmoPlayer.getPlayer()).thenReturn(player); @@ -69,10 +69,9 @@ class PartyManagerTest { @Test public void createPartyWithoutNameShouldFail() { // Given - PartyManager partyManager = new PartyManager(mockMcMMO); + PartyManager partyManager = new PartyManager(mcMMO.p); String partyPassword = "somePassword"; - // TODO: Update this with utils from the other dev branches in the future Player player = mock(Player.class); McMMOPlayer mmoPlayer = mock(McMMOPlayer.class); when(mmoPlayer.getPlayer()).thenReturn(player); @@ -86,7 +85,7 @@ class PartyManagerTest { @Test public void createPartyWithoutPlayerShouldFail() { // Given - PartyManager partyManager = new PartyManager(mockMcMMO); + PartyManager partyManager = new PartyManager(mcMMO.p); String partyName = "TestParty"; String partyPassword = "somePassword"; diff --git a/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java b/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java new file mode 100644 index 000000000..631696f0a --- /dev/null +++ b/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java @@ -0,0 +1,120 @@ +package com.gmail.nossr50.skills.excavation; + +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.treasure.ExcavationTreasure; +import com.gmail.nossr50.util.skills.RankUtils; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +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; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +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); + when(rankConfig.getSubSkillUnlockLevel(SubSkillType.EXCAVATION_ARCHAEOLOGY, 1)).thenReturn(1); + when(rankConfig.getSubSkillUnlockLevel(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER, 1)).thenReturn(1); + + // wire advanced config + + when(RankUtils.getRankUnlockLevel(SubSkillType.EXCAVATION_ARCHAEOLOGY, 1)).thenReturn(1); // needed? + when(RankUtils.getRankUnlockLevel(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER, 1)).thenReturn(1); // needed? + when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.EXCAVATION_ARCHAEOLOGY))).thenReturn(true); + when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER))).thenReturn(true); + + // setup player and player related mocks after everything else + this.player = Mockito.mock(Player.class); + 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(); + } + + @Test + void excavationShouldHaveTreasureDrops() { + mmoPlayer.modifySkill(PrimarySkillType.EXCAVATION, 1000); + + // Wire block + BlockState blockState = Mockito.mock(BlockState.class); + BlockData blockData = Mockito.mock(BlockData.class); + Block block = Mockito.mock(Block.class); + when(blockState.getBlockData()).thenReturn(blockData); + when(blockState.getType()).thenReturn(Material.SAND); + when(blockData.getMaterial()).thenReturn(Material.SAND); + when(blockState.getBlock()).thenReturn(block); + when(blockState.getBlock().getDrops(any())).thenReturn(null); + + ExcavationManager excavationManager = Mockito.spy(new ExcavationManager(mmoPlayer)); + doReturn(getGuaranteedTreasureDrops()).when(excavationManager).getTreasures(blockState); + excavationManager.excavationBlockCheck(blockState); + + // verify ExcavationManager.processExcavationBonusesOnBlock was called + verify(excavationManager, atLeastOnce()).processExcavationBonusesOnBlock(any(BlockState.class), any(ExcavationTreasure.class), any(Location.class)); + } + + @Test + void excavationShouldNotDropTreasure() { + mmoPlayer.modifySkill(PrimarySkillType.EXCAVATION, 1000); + + // Wire block + BlockState blockState = Mockito.mock(BlockState.class); + BlockData blockData = Mockito.mock(BlockData.class); + Block block = Mockito.mock(Block.class); + when(blockState.getBlockData()).thenReturn(blockData); + when(blockState.getType()).thenReturn(Material.SAND); + when(blockData.getMaterial()).thenReturn(Material.SAND); + when(blockState.getBlock()).thenReturn(block); + when(blockState.getBlock().getDrops(any())).thenReturn(null); + + ExcavationManager excavationManager = Mockito.spy(new ExcavationManager(mmoPlayer)); + doReturn(getImpossibleTreasureDrops()).when(excavationManager).getTreasures(blockState); + excavationManager.excavationBlockCheck(blockState); + + // verify ExcavationManager.processExcavationBonusesOnBlock was called + verify(excavationManager, never()).processExcavationBonusesOnBlock(any(BlockState.class), any(ExcavationTreasure.class), any(Location.class)); + } + + private List getGuaranteedTreasureDrops() { + List treasures = new ArrayList<>();; + treasures.add(new ExcavationTreasure(new ItemStack(Material.CAKE), 1, 100, 1)); + return treasures; + } + + private List getImpossibleTreasureDrops() { + List treasures = new ArrayList<>();; + treasures.add(new ExcavationTreasure(new ItemStack(Material.CAKE), 1, 0, 1)); + return treasures; + } +} diff --git a/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java b/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java new file mode 100644 index 000000000..199c8e554 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java @@ -0,0 +1,39 @@ +package com.gmail.nossr50.skills.tridents; + +import com.gmail.nossr50.MMOTestEnvironment; +import com.gmail.nossr50.api.exceptions.InvalidSkillException; +import org.bukkit.Material; +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.mockito.Mockito; + +class TridentsTest extends MMOTestEnvironment { + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(TridentsTest.class.getName()); + + TridentsManager tridentsManager; + ItemStack trident; + @BeforeEach + void setUp() throws InvalidSkillException { + mockBaseEnvironment(logger); + + // 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.trident = new ItemStack(Material.TRIDENT); + Mockito.when(playerInventory.getItemInMainHand()).thenReturn(trident); + + // Set up spy for manager + tridentsManager = Mockito.spy(new TridentsManager(mmoPlayer)); + } + + @AfterEach + void tearDown() { + cleanupBaseEnvironment(); + } +} diff --git a/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java b/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java new file mode 100644 index 000000000..2715ead65 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java @@ -0,0 +1,108 @@ +package com.gmail.nossr50.skills.woodcutting; + +import com.gmail.nossr50.MMOTestEnvironment; +import com.gmail.nossr50.api.exceptions.InvalidSkillException; +import com.gmail.nossr50.config.experience.ExperienceConfig; +import com.gmail.nossr50.datatypes.skills.PrimarySkillType; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.util.skills.RankUtils; +import org.bukkit.Material; +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 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()); + + WoodcuttingManager woodcuttingManager; + @BeforeEach + void setUp() throws InvalidSkillException { + mockBaseEnvironment(logger); + Mockito.when(rankConfig.getSubSkillUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1); + + // wire advanced config + Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(100D); + Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10D); + Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(1000); + Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10000); + + Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1); // needed? + Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS, 1)).thenReturn(1000); // needed? + 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); + + // Set up spy for WoodcuttingManager + woodcuttingManager = Mockito.spy(new WoodcuttingManager(mmoPlayer)); + } + + @AfterEach + void tearDown() { + cleanupBaseEnvironment(); + } + + @Test + void harvestLumberShouldDoubleDrop() { + mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 1000); + + BlockState blockState = Mockito.mock(BlockState.class); + Block block = Mockito.mock(Block.class); + // wire block + Mockito.when(blockState.getBlock()).thenReturn(block); + + Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null); + Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG); + woodcuttingManager.processBonusDropCheck(blockState); + + // verify bonus drops were spawned + // TODO: Can fail if triple drops happen, need to update test + Mockito.verify(woodcuttingManager, Mockito.times(1)).spawnHarvestLumberBonusDrops(blockState); + } + + @Test + void harvestLumberShouldNotDoubleDrop() { + mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 0); + + BlockState blockState = Mockito.mock(BlockState.class); + Block block = Mockito.mock(Block.class); + // wire block + Mockito.when(blockState.getBlock()).thenReturn(block); + + Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null); + Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG); + woodcuttingManager.processBonusDropCheck(blockState); + + // verify bonus drops were not spawned + Mockito.verify(woodcuttingManager, Mockito.times(0)).spawnHarvestLumberBonusDrops(blockState); + } + + @Test + void testProcessWoodcuttingBlockXP() { + BlockState targetBlock = Mockito.mock(BlockState.class); + Mockito.when(targetBlock.getType()).thenReturn(Material.OAK_LOG); + // wire XP + Mockito.when(ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, Material.OAK_LOG)).thenReturn(5); + + // Verify XP increased by 5 when processing XP + woodcuttingManager.processWoodcuttingBlockXP(targetBlock); + Mockito.verify(mmoPlayer, Mockito.times(1)).beginXpGain(eq(PrimarySkillType.WOODCUTTING), eq(5F), any(), any()); + } +} diff --git a/src/test/java/com/gmail/nossr50/util/random/ProbabilityTest.java b/src/test/java/com/gmail/nossr50/util/random/ProbabilityTest.java new file mode 100644 index 000000000..a2b6cc416 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/util/random/ProbabilityTest.java @@ -0,0 +1,105 @@ +package com.gmail.nossr50.util.random; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +class ProbabilityTest { + + private static Stream provideProbabilitiesForWithinExpectations() { + return Stream.of( + // static probability, % of time for success + Arguments.of(new ProbabilityImpl(5), 5), + Arguments.of(new ProbabilityImpl(10), 10), + Arguments.of(new ProbabilityImpl(15), 15), + Arguments.of(new ProbabilityImpl(20), 20), + Arguments.of(new ProbabilityImpl(25), 25), + Arguments.of(new ProbabilityImpl(50), 50), + Arguments.of(new ProbabilityImpl(75), 75), + Arguments.of(new ProbabilityImpl(90), 90), + Arguments.of(new ProbabilityImpl(99.9), 99.9), + Arguments.of(new ProbabilityImpl(0.05), 0.05), + Arguments.of(new ProbabilityImpl(0.1), 0.1), + Arguments.of(new ProbabilityImpl(500), 100), + Arguments.of(new ProbabilityImpl(1000), 100) + ); + } + + private static Stream provideOfPercentageProbabilitiesForWithinExpectations() { + return Stream.of( + // static probability, % of time for success + Arguments.of(Probability.ofPercent(5), 5), + Arguments.of(Probability.ofPercent(10), 10), + Arguments.of(Probability.ofPercent(15), 15), + Arguments.of(Probability.ofPercent(20), 20), + Arguments.of(Probability.ofPercent(25), 25), + Arguments.of(Probability.ofPercent(50), 50), + Arguments.of(Probability.ofPercent(75), 75), + Arguments.of(Probability.ofPercent(90), 90), + Arguments.of(Probability.ofPercent(99.9), 99.9), + Arguments.of(Probability.ofPercent(0.05), 0.05), + Arguments.of(Probability.ofPercent(0.1), 0.1), + Arguments.of(Probability.ofPercent(500), 100), + Arguments.of(Probability.ofPercent(1000), 100) + ); + } + @Test + void testAlwaysWinConstructor() { + for (int i = 0; i < 100000; i++) { + assertTrue(new ProbabilityImpl(100).evaluate()); + } + } + + @Test + void testAlwaysLoseConstructor() { + for (int i = 0; i < 100000; i++) { + assertFalse(new ProbabilityImpl(0).evaluate()); + } + } + + @Test + void testAlwaysWinOfPercent() { + for (int i = 0; i < 100000; i++) { + assertTrue(Probability.ofPercent(100).evaluate()); + } + } + + @Test + void testAlwaysLoseOfPercent() { + for (int i = 0; i < 100000; i++) { + assertFalse(Probability.ofPercent(0).evaluate()); + } + } + + @ParameterizedTest + @MethodSource("provideProbabilitiesForWithinExpectations") + void testOddsExpectationsConstructor(Probability probability, double expectedWinPercent) { + assertExpectations(probability, expectedWinPercent); + } + + @ParameterizedTest + @MethodSource("provideOfPercentageProbabilitiesForWithinExpectations") + void testOddsExpectationsOfPercent(Probability probability, double expectedWinPercent) { + assertExpectations(probability, expectedWinPercent); + } + + private static void assertExpectations(Probability probability, double expectedWinPercent) { + double iterations = 2.0e7; + double winCount = 0; + + for (int i = 0; i < iterations; i++) { + if(probability.evaluate()) { + winCount++; + } + } + + double successPercent = (winCount / iterations) * 100; + System.out.println(successPercent + ", " + expectedWinPercent); + assertEquals(expectedWinPercent, successPercent, 0.05D); + } +} diff --git a/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java b/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java new file mode 100644 index 000000000..35f134da5 --- /dev/null +++ b/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java @@ -0,0 +1,66 @@ +package com.gmail.nossr50.util.random; + +import com.gmail.nossr50.config.AdvancedConfig; +import com.gmail.nossr50.datatypes.skills.SubSkillType; +import com.gmail.nossr50.mcMMO; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static com.gmail.nossr50.datatypes.skills.SubSkillType.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class ProbabilityUtilTest { + mcMMO mmoInstance; + AdvancedConfig advancedConfig; + + final static double impactChance = 11D; + final static double greaterImpactChance = 0.007D; + final static double fastFoodChance = 45.5D; + + @BeforeEach + public void setupMocks() throws NoSuchFieldException, IllegalAccessException { + this.mmoInstance = mock(mcMMO.class); + mcMMO.class.getField("p").set(null, mmoInstance); + this.advancedConfig = mock(AdvancedConfig.class); + when(mmoInstance.getAdvancedConfig()).thenReturn(advancedConfig); + when(advancedConfig.getImpactChance()).thenReturn(impactChance); + when(advancedConfig.getGreaterImpactChance()).thenReturn(greaterImpactChance); + when(advancedConfig.getFastFoodChance()).thenReturn(fastFoodChance); + } + + private static Stream staticChanceSkills() { + return Stream.of( + // static probability, % of time for success + Arguments.of(AXES_ARMOR_IMPACT, impactChance), + Arguments.of(AXES_GREATER_IMPACT, greaterImpactChance), + Arguments.of(TAMING_FAST_FOOD_SERVICE, fastFoodChance) + ); + } + + @ParameterizedTest + @MethodSource("staticChanceSkills") + void testStaticChanceSkills(SubSkillType subSkillType, double expectedWinPercent) throws InvalidStaticChance { + Probability staticRandomChance = ProbabilityUtil.getStaticRandomChance(subSkillType); + assertProbabilityExpectations(expectedWinPercent, staticRandomChance); + } + + private static void assertProbabilityExpectations(double expectedWinPercent, Probability probability) { + double iterations = 2.0e7; + double winCount = 0; + for (int i = 0; i < iterations; i++) { + if(probability.evaluate()) { + winCount++; + } + } + + double successPercent = (winCount / iterations) * 100; + System.out.println(successPercent + ", " + expectedWinPercent); + assertEquals(expectedWinPercent, successPercent, 0.05D); + } +} diff --git a/src/test/java/com/gmail/nossr50/util/random/RandomChanceTest.java b/src/test/java/com/gmail/nossr50/util/random/RandomChanceTest.java deleted file mode 100644 index f28e7e842..000000000 --- a/src/test/java/com/gmail/nossr50/util/random/RandomChanceTest.java +++ /dev/null @@ -1,116 +0,0 @@ -//package com.gmail.nossr50.util.random; -// -//import com.gmail.nossr50.datatypes.player.McMMOPlayer; -//import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -//import com.gmail.nossr50.datatypes.skills.SubSkillType; -//import com.gmail.nossr50.util.Permissions; -//import com.gmail.nossr50.util.player.UserManager; -//import org.bukkit.entity.Player; -//import org.jetbrains.annotations.NotNull; -//import org.junit.Assert; -//import org.junit.Before; -//import org.junit.Test; -//import org.junit.runner.RunWith; -//import org.mockito.Mockito; -//import org.powermock.api.mockito.PowerMockito; -//import org.powermock.core.classloader.annotations.PrepareForTest; -//import org.powermock.modules.junit4.PowerMockRunner; -// -//import static org.mockito.Mockito.mock; -// -////TODO: Rewrite the entire com.gmail.nossr50.util.random package, it was written in haste and it disgusts me -////TODO: Add more tests for the other types of random dice rolls -//@RunWith(PowerMockRunner.class) -//@PrepareForTest({RandomChanceUtil.class, UserManager.class}) -//public class RandomChanceTest { -// -// private Player luckyPlayer; -// private McMMOPlayer mmoPlayerLucky; -// -// private Player normalPlayer; -// private McMMOPlayer mmoPlayerNormal; -// -// private SubSkillType subSkillType; -// private PrimarySkillType primarySkillType; -// -// private final String testASCIIHeader = "---- mcMMO Tests ----"; -// -// @Before -// public void setUpMock() { -// primarySkillType = PrimarySkillType.HERBALISM; -// subSkillType = SubSkillType.HERBALISM_GREEN_THUMB; -// -// //TODO: Likely needs to be changed per skill if more tests were added -// PowerMockito.stub(PowerMockito.method(RandomChanceUtil.class, "getMaximumProbability", subSkillType.getClass())).toReturn(100D); -// PowerMockito.stub(PowerMockito.method(RandomChanceUtil.class, "getMaxBonusLevelCap", subSkillType.getClass())).toReturn(1000D); -// -// normalPlayer = mock(Player.class); -// luckyPlayer = mock(Player.class); -// -// mmoPlayerNormal = mock(McMMOPlayer.class); -// mmoPlayerLucky = mock(McMMOPlayer.class); -// -// PowerMockito.mockStatic(UserManager.class); -// Mockito.when(UserManager.getPlayer(normalPlayer)).thenReturn(mmoPlayerNormal); -// Mockito.when(UserManager.getPlayer(luckyPlayer)).thenReturn(mmoPlayerLucky); -// -// Mockito.when(mmoPlayerNormal.getPlayer()).thenReturn(normalPlayer); -// Mockito.when(mmoPlayerLucky.getPlayer()).thenReturn(luckyPlayer); -// -// //Lucky player has the lucky permission -// //Normal player doesn't have any lucky permission -// Mockito.when(Permissions.lucky(luckyPlayer, primarySkillType)).thenReturn(true); -// Mockito.when(Permissions.lucky(normalPlayer, primarySkillType)).thenReturn(false); -// -// Mockito.when(mmoPlayerNormal.getSkillLevel(primarySkillType)).thenReturn(800); -// Mockito.when(mmoPlayerLucky.getSkillLevel(primarySkillType)).thenReturn(800); -// } -// -// @Test -// public void testLuckyChance() { -// System.out.println(testASCIIHeader); -// System.out.println("Testing success odds to fall within expected values..."); -// assertEquals(80D, getSuccessChance(mmoPlayerNormal),0D); -// assertEquals(80D * RandomChanceUtil.LUCKY_MODIFIER, getSuccessChance(mmoPlayerLucky),0D); -// } -// -// @Test -// public void testNeverFailsSuccessLuckyPlayer() { -// System.out.println(testASCIIHeader); -// System.out.println("Test - Lucky Player with 80% base success should never fail (10,000 iterations)"); -// for(int x = 0; x < 10000; x++) { -// Assert.assertTrue(RandomChanceUtil.checkRandomChanceExecutionSuccess(luckyPlayer, SubSkillType.HERBALISM_GREEN_THUMB, true)); -// if(x == 10000-1) -// System.out.println("They never failed!"); -// } -// } -// -// @Test -// public void testFailsAboutExpected() { -// System.out.println(testASCIIHeader); -// System.out.println("Test - Player with 800 skill should fail about 20% of the time (100,000 iterations)"); -// double ratioDivisor = 1000; //1000 because we run the test 100,000 times -// double expectedFailRate = 20D; -// -// double win = 0, loss = 0; -// for(int x = 0; x < 100000; x++) { -// if(RandomChanceUtil.checkRandomChanceExecutionSuccess(normalPlayer, SubSkillType.HERBALISM_GREEN_THUMB, true)) { -// win++; -// } else { -// loss++; -// } -// } -// -// double lossRatio = (loss / ratioDivisor); -// Assert.assertEquals(lossRatio, expectedFailRate, 1D); -// } -// -// private double getSuccessChance(@NotNull McMMOPlayer mmoPlayer) { -// RandomChanceSkill randomChanceSkill = new RandomChanceSkill(mmoPlayer.getPlayer(), subSkillType, true); -// return RandomChanceUtil.calculateChanceOfSuccess(randomChanceSkill); -// } -// -// private void assertEquals(double expected, double actual, double delta) { -// Assert.assertEquals(expected, actual, delta); -// } -//} diff --git a/src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java b/src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java deleted file mode 100644 index 4a295d5d3..000000000 --- a/src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java +++ /dev/null @@ -1,16 +0,0 @@ -//package com.gmail.nossr50.util.skills; -// -//import com.gmail.nossr50.datatypes.skills.PrimarySkillType; -//import com.google.common.collect.ImmutableList; -//import org.junit.Before; -//import org.junit.Test; -//import org.junit.runner.RunWith; -//import org.powermock.core.classloader.annotations.PrepareForTest; -//import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor; -//import org.powermock.modules.junit4.PowerMockRunner; -// -//@RunWith(PowerMockRunner.class) -//@PrepareForTest(SkillTools.class) -//public class SkillToolsTest { -// -//} \ No newline at end of file diff --git a/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java b/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java index cefbe7010..c58157179 100644 --- a/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java +++ b/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.Test; /** * This Unit Test checks if Adventure was set up correctly and works as expected. - * Normally we can rely on this to be the case. However sometimes our dependencies + * Normally, we can rely on this to be the case. However sometimes our dependencies * lack so far behind that things stop working correctly. * This test ensures that basic functionality is guaranteed to work as we would expect. * diff --git a/src/test/resources/healthydb.users b/src/test/resources/healthydb.users index 7ce5ccbad..79a2c7e70 100644 --- a/src/test/resources/healthydb.users +++ b/src/test/resources/healthydb.users @@ -1,3 +1,3 @@ -nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020: -mrfloris:2420:::0:2452:0:1983:1937:1790:3042:1138:3102:2408:3411:0:0:0:0:0:0:0:0::642:0:1617583171:0:1617165043:0:1617583004:1617563189:1616785408::2184:0:0:1617852413:HEARTS:415:0:631e3896-da2a-4077-974b-d047859d76bc:5:1600906906:3030: -powerless:0:::0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0::0:0:0:0:0:0:0:0:0::0:0:0:1337:HEARTS:0:0:e0d07db8-f7e8-43c7-9ded-864dfc6f3b7c:5:1600906906:4040: \ No newline at end of file +nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:140:14:150:15:1111:2222:3333: +mrfloris:2420:::0:2452:0:1983:1937:1790:3042:1138:3102:2408:3411:0:0:0:0:0:0:0:0::642:0:1617583171:0:1617165043:0:1617583004:1617563189:1616785408::2184:0:0:1617852413:HEARTS:415:0:631e3896-da2a-4077-974b-d047859d76bc:5:1600906906:3030:0:0:0:0:0:0:0: +powerless:0:::0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0::0:0:0:0:0:0:0:0:0::0:0:0:1337:HEARTS:0:0:e0d07db8-f7e8-43c7-9ded-864dfc6f3b7c:5:1600906906:4040:0:0:0:0:0:0:0: \ No newline at end of file diff --git a/src/test/resources/olderdb.users b/src/test/resources/olderdb.users new file mode 100644 index 000000000..7ce5ccbad --- /dev/null +++ b/src/test/resources/olderdb.users @@ -0,0 +1,3 @@ +nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020: +mrfloris:2420:::0:2452:0:1983:1937:1790:3042:1138:3102:2408:3411:0:0:0:0:0:0:0:0::642:0:1617583171:0:1617165043:0:1617583004:1617563189:1616785408::2184:0:0:1617852413:HEARTS:415:0:631e3896-da2a-4077-974b-d047859d76bc:5:1600906906:3030: +powerless:0:::0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0::0:0:0:0:0:0:0:0:0::0:0:0:1337:HEARTS:0:0:e0d07db8-f7e8-43c7-9ded-864dfc6f3b7c:5:1600906906:4040: \ No newline at end of file